/** * Utilities to clean up resources when the application is stopping. Currenly, when a ZK application * is stopping, ZK will invoke {@link #cleanup}. Thus you can register an implementation of {@link * Cleanup} to release the resources. * * <p>Notice that this utility is introduced mainly to resolve the memory lead issue if an * application is hot re-deployed. A typical example is to stop any pooled threads. * * <h3>How to use:</h3> * * <p>First, register the cleanup with {@link #add}. * * <p>Second, invoke {@link #cleanup} when necessary, such as when the application is stopping. * * @author tomyeh * @since 3.6.5 */ public class Cleanups { private static final Log log = Log.lookup(Cleanups.class); private static final List _cleanups = new LinkedList(); /** * Registers a cleanup. * * @return true if it is added successfully, or false if the cleanup has been regsitered before. */ public static boolean add(Cleanup cleanup) { if (cleanup == null) throw new IllegalArgumentException(); synchronized (_cleanups) { if (_cleanups.contains(cleanup)) return false; return _cleanups.add(cleanup); } } /** * Un-registers a cleanup. * * @return true if it is removed successfully, or false if not registered before. */ public static boolean remove(Cleanup cleanup) { synchronized (_cleanups) { return _cleanups.remove(cleanup); } } /** Invokes all cleanups registered with {@link #add}. */ public static void cleanup() { final List cleanups; synchronized (_cleanups) { cleanups = new ArrayList(_cleanups); } for (Iterator it = cleanups.iterator(); it.hasNext(); ) { final Cleanup cleanup = (Cleanup) it.next(); try { cleanup.cleanup(); } catch (Throwable ex) { log.error("Failed to invoke " + cleanup); } } } /** * The interface to implement for each cleanup. It is used with {@link Cleanups#add}. * * @since 3.6.5 */ public static interface Cleanup { /** Cleanups the thread local variables. */ public void cleanup(); } }
/** * An implementation of {@link RequestQueue} behaving as a queue of {@link AuRequest}. There is one * queue for each desktop. * * <p>Implementation Note: Unlike only of desktop members, this class must be thread-safe. * * @author tomyeh */ public class RequestQueueImpl implements RequestQueue { private static final Log log = Log.lookup(RequestQueueImpl.class); /** A list of pending {@link AuRequest}. */ private final List _requests = new LinkedList(); /** A list of request ID for performance measurement. */ private List _pfreqIds; // -- RequestQueue --// public void addPerfRequestId(String requestId) { if (_pfreqIds == null) _pfreqIds = new LinkedList(); _pfreqIds.add(requestId); } public Collection clearPerfRequestIds() { final List old = _pfreqIds; _pfreqIds = null; return old; } public boolean isEmpty() { while (!_requests.isEmpty()) { if (isObsolete((AuRequest) _requests.get(0))) _requests.remove(0); else return false; } return true; } private static boolean isObsolete(AuRequest request) { final Component comp = request.getComponent(); if (comp != null) { final Desktop dt = comp.getDesktop(); return dt != null && dt != request.getDesktop(); // Since 3.5.0, we allow a component to process events even // if it is detached (due to implementation of EventQueue) } return false; } public AuRequest nextRequest() { while (!_requests.isEmpty()) { final AuRequest request = (AuRequest) _requests.remove(0); if (!isObsolete(request)) return request; } return null; } public void addRequests(Collection requests) { for (Iterator it = requests.iterator(); it.hasNext(); ) { final AuRequest request = (AuRequest) it.next(); try { request.activate(); if (!isObsolete(request)) addRequest(request); } catch (ComponentNotFoundException ex) { // ignore it // ignore it since a long request might remove a timer // while clients already queues onTimer generated by the timer if (log.debugable()) log.debug("Ignore request: " + ex.getMessage()); } } } private void addRequest(AuRequest request) { // case 1, BUSY_IGNORE: Drop any existent ignorable requests // We don't need to iterate all because requests is added one-by-one // In other words, if any temporty request, it must be the last { int last = _requests.size() - 1; if (last < 0) { // optimize the most common case _requests.add(request); return; } final AuRequest req2 = (AuRequest) _requests.get(last); if ((req2.getOptions() & AuRequest.BUSY_IGNORE) != 0) { _requests.remove(last); // drop it if (last == 0) { _requests.add(request); return; } } } final String name = request.getCommand(); final int opts = request.getOptions(); // Since 3.0.2, redundant CTRL_GROUP is removed at the client // case 2, DUPLICATE_IGNORE: drop existent request if they are the same // as the arrival. if ((opts & AuRequest.DUPLICATE_IGNORE) != 0) { final String uuid = getUuid(request); for (Iterator it = _requests.iterator(); it.hasNext(); ) { final AuRequest req2 = (AuRequest) it.next(); if (req2.getCommand().equals(name) && Objects.equals(getUuid(req2), uuid)) { it.remove(); // drop req2 (the old one) break; // no need to iterate because impossible to have more } } // Case 3, REPEAT_IGNORE: drop existent if the immediate // following is the same } else if ((opts & AuRequest.REPEAT_IGNORE) != 0) { final int last = _requests.size() - 1; final AuRequest req2 = (AuRequest) _requests.get(last); if (req2.getCommand().equals(name) && Objects.equals(getUuid(req2), getUuid(request))) { _requests.remove(last); } } _requests.add(request); } private String getUuid(AuRequest request) { final Component comp = request.getComponent(); if (comp != null) return comp.getUuid(); final Page page = request.getPage(); if (page != null) return page.getUuid(); return null; } }
/** * Listener to make sure each ZK thread got the same ThreadLocal value of the spring's * org.springframework.transaction.support.TransactionSynchronizationManager; especially those * thread bound resources. * * <p>This listener is used with Spring Framework (version 1.2.8+) "thread" bounded resources. * * <pre><code> * <listener> * <description>Spring TransactionSynchronizationManager handler</description> * <listener-class>org.zkoss.zkplus.spring.SpringTransactionSynchronizationListener</listener-class> * </listener> * </code></pre> * * <p>Applicable to Spring Framework version 2.x or later * * @author henrichen */ public class SpringTransactionSynchronizationListener implements EventThreadInit, EventThreadCleanup, EventThreadResume { private static final Log log = Log.lookup(SpringTransactionSynchronizationListener.class); private Object[] _threadLocals = null; private final boolean _enabled; // whether event thread enabled public SpringTransactionSynchronizationListener() { final WebApp app = Executions.getCurrent().getDesktop().getWebApp(); _enabled = app.getConfiguration().isEventThreadEnabled(); } // -- EventThreadInit --// public void prepare(Component comp, Event evt) { if (_enabled) { getThreadLocals(); // get from servlet thread's ThreadLocal } } public boolean init(Component comp, Event evt) { if (_enabled) { setThreadLocals(); // copy to event thread's ThreadLocal } return true; } // -- EventThreadCleanup --// public void cleanup(Component comp, Event evt, List errs) { if (_enabled) { getThreadLocals(); // get from event thread's ThreadLocal // we don't handle the exception since the ZK engine will throw it again! } } public void complete(Component comp, Event evt) { if (_enabled) { setThreadLocals(); // copy to servlet thread's ThreadLocal } } // -- EventThreadResume --// public void beforeResume(Component comp, Event evt) { if (_enabled) { getThreadLocals(); // get from servlet thread's ThreadLocal } } public void afterResume(Component comp, Event evt) { if (_enabled) { setThreadLocals(); // copy to event thread's ThreadLocal } } public void abortResume(Component comp, Event evt) { // do nothing } // -- utilities --// private void getThreadLocals() { try { Class cls = Classes.forNameByThread( "org.springframework.transaction.support.TransactionSynchronizationManager"); _threadLocals = new Object[7]; _threadLocals[0] = getThreadLocal(cls, "resources").get(); _threadLocals[1] = getThreadLocal(cls, "synchronizations").get(); _threadLocals[2] = getThreadLocal(cls, "currentTransactionName").get(); _threadLocals[3] = getThreadLocal(cls, "currentTransactionReadOnly").get(); _threadLocals[4] = getThreadLocal(cls, "actualTransactionActive").get(); // 20070907, Henri Chen: bug 1785457, hibernate3 might not used try { cls = Classes.forNameByThread("org.springframework.orm.hibernate3.SessionFactoryUtils"); _threadLocals[5] = getThreadLocal(cls, "deferredCloseHolder").get(); } catch (ClassNotFoundException ex) { // ignore if hibernate 3 is not used. } cls = Classes.forNameByThread( "org.springframework.transaction.interceptor.TransactionAspectSupport"); // Spring 1.2.8 and Spring 2.0.x, the ThreadLocal field name has changed, default use 2.0.x // 2.0.x transactionInfoHolder // 1.2.8 currentTransactionInfo try { _threadLocals[6] = getThreadLocal(cls, "transactionInfoHolder").get(); } catch (SystemException ex) { if (ex.getCause() instanceof NoSuchFieldException) { _threadLocals[6] = getThreadLocal(cls, "currentTransactionInfo").get(); } else { throw ex; } } } catch (ClassNotFoundException ex) { throw UiException.Aide.wrap(ex); } } @SuppressWarnings("unchecked") private void setThreadLocals() { if (_threadLocals != null) { try { Class cls = Classes.forNameByThread( "org.springframework.transaction.support.TransactionSynchronizationManager"); getThreadLocal(cls, "resources").set(_threadLocals[0]); getThreadLocal(cls, "synchronizations").set(_threadLocals[1]); getThreadLocal(cls, "currentTransactionName").set(_threadLocals[2]); getThreadLocal(cls, "currentTransactionReadOnly").set(_threadLocals[3]); getThreadLocal(cls, "actualTransactionActive").set(_threadLocals[4]); // 20070907, Henri Chen: bug 1785457, hibernate3 might not used try { cls = Classes.forNameByThread("org.springframework.orm.hibernate3.SessionFactoryUtils"); getThreadLocal(cls, "deferredCloseHolder").set(_threadLocals[5]); } catch (ClassNotFoundException ex) { // ignore if hibernate 3 is not used. } cls = Classes.forNameByThread( "org.springframework.transaction.interceptor.TransactionAspectSupport"); // Spring 1.2.8 and Spring 2.0.x, the ThreadLocal field name has changed, default use 2.0.x // 2.0.x transactionInfoHolder // 1.2.8 currentTransactionInfo try { getThreadLocal(cls, "transactionInfoHolder").set(_threadLocals[6]); } catch (SystemException ex) { if (ex.getCause() instanceof NoSuchFieldException) { getThreadLocal(cls, "currentTransactionInfo").set(_threadLocals[6]); } else { throw ex; } } _threadLocals = null; } catch (ClassNotFoundException ex) { throw UiException.Aide.wrap(ex); } } } private ThreadLocal getThreadLocal(Class cls, String fldname) { return ThreadLocals.getThreadLocal(cls, fldname); } }
/** * Skeletal implementation for {@link WpdExtendlet} and {@link WcsExtendlet}. * * @author tomyeh * @since 5.0.0 */ /*package*/ abstract class AbstractExtendlet<V> implements Extendlet { static final Log log = Log.lookup(AbstractExtendlet.class); ExtendletContext _webctx; /** DSP interpretation cache. */ ResourceCache<String, V> _cache; /** The provider. */ private ThreadLocal<Provider> _provider = new ThreadLocal<Provider>(); private Boolean _debugJS; // Public Utilities /** Sets whether to generate JS files that is easy to debug. */ public void setDebugJS(boolean debugJS) { _debugJS = Boolean.valueOf(debugJS); if (_cache != null) _cache.clear(); } /** Returns whether to generate JS files that is easy to debug. */ public boolean isDebugJS() { if (_debugJS == null) { final WebApp wapp = getWebApp(); if (wapp == null) return true; // zk lighter _debugJS = Boolean.valueOf(wapp.getConfiguration().isDebugJS()); } return _debugJS.booleanValue(); } // Package Utilities Provider getProvider() { return _provider.get(); } void setProvider(Provider provider) { _provider.set(provider); } WebApp getWebApp() { return _webctx != null ? WebManager.getWebManager(_webctx.getServletContext()).getWebApp() : null; } ServletContext getServletContext() { return _webctx != null ? _webctx.getServletContext() : null; } void init(ExtendletConfig config, ExtendletLoader<V> loader) { _webctx = config.getExtendletContext(); _cache = new ResourceCache<String, V>(loader, 16); _cache.setMaxSize(1024); _cache.setLifetime(60 * 60 * 1000); // 1hr final int checkPeriod = loader.getCheckPeriod(); _cache.setCheckPeriod(checkPeriod >= 0 ? checkPeriod : 60 * 60 * 1000); // 1hr } /** Returns the static method defined in an element, or null if failed. */ /*package*/ static MethodInfo getMethodInfo(Element el) { final String clsnm = IDOMs.getRequiredAttributeValue(el, "class"); final String sig = IDOMs.getRequiredAttributeValue(el, "signature"); final Class cls; try { cls = Classes.forNameByThread(clsnm); } catch (ClassNotFoundException ex) { log.error("Class not found: " + clsnm + ", " + el.getLocator()); return null; // to report as many errors as possible } try { final Method mtd = Classes.getMethodBySignature(cls, sig, null); if ((mtd.getModifiers() & Modifier.STATIC) == 0) { log.error("Not a static method: " + mtd); return null; } final Object[] args = new Object[mtd.getParameterTypes().length]; for (int j = 0; j < args.length; ++j) args[j] = el.getAttributeValue("arg" + j); return new MethodInfo(mtd, args); } catch (ClassNotFoundException ex) { log.realCauseBriefly( "Unable to load class when resolving " + sig + " " + el.getLocator(), ex); } catch (NoSuchMethodException ex) { log.error("Method not found in " + clsnm + ": " + sig + " " + el.getLocator()); } return null; } /** Invokes a static method. */ /*package*/ String invoke(MethodInfo mi) { final Provider provider = getProvider(); final Class[] argTypes = mi.method.getParameterTypes(); final Object[] args = mi.arguments; if (provider != null) for (int j = 0; j < args.length; ++j) if (ServletRequest.class.isAssignableFrom(argTypes[j])) args[j] = provider.request; else if (ServletResponse.class.isAssignableFrom(argTypes[j])) args[j] = provider.response; else if (ServletContext.class.isAssignableFrom(argTypes[j])) args[j] = getServletContext(); try { Object o = mi.method.invoke(null, args); return o instanceof String ? (String) o : ""; } catch (Throwable ex) { // log and eat ex log.error("Unable to invoke " + mi.method, ex); return ""; } } // Extendlet public boolean getFeature(int feature) { return feature == ALLOW_DIRECT_INCLUDE; } /*package*/ // called by Provider InputStream getResourceAsStream(HttpServletRequest request, String path, boolean locate) throws IOException, ServletException { if (locate) path = Servlets.locate(_webctx.getServletContext(), request, path, _webctx.getLocator()); if (_cache.getCheckPeriod() >= 0) { // Due to Web server might cache the result, we use URL if possible try { URL url = _webctx.getResource(path); if (url != null) return url.openStream(); } catch (Throwable ex) { log.warningBriefly("Unable to read from URL: " + path, ex); } } // Note: _webctx will handle the renaming for debugJS (.src.js) return _webctx.getResourceAsStream(path); } /*package*/ // called by Provider URL getResource(String path) throws IOException { return _webctx.getResource(path); } // utility class /*package*/ static class MethodInfo { final Method method; final Object[] arguments; MethodInfo(Method method, Object[] arguments) { this.method = method; this.arguments = arguments; } } }
/** * Used to receive command from the server and send result back to client. Though it is called * DHtmlUpdateServlet, it is used to serve all kind of HTTP-based clients, including ajax * (HTML+Ajax), mil (Mobile Interactive Language), and others (see {@link Desktop#getDeviceType}. * * <p>Init parameters: * * <dl> * <dt>compress * <dd>It specifies whether to compress the output if the browser supports the compression * (Accept-Encoding). * <dt>extension0, extension1... * <dd>It specifies an AU extension ({@link AuExtension}). The <code>extension0</code> parameter * specifies the first AU extension, the <code>extension1</code> parameter the second AU * extension, and so on.<br> * The syntax of the value is<br> * <code>/prefix=class</code> * </dl> * * <p>By default: there are two extensions are associated with "/upload" and "/view" (see {@link * #addAuExtension}. Also, "/web" is reserved. Don't associate to any AU extension. * * @author tomyeh */ public class DHtmlUpdateServlet extends HttpServlet { private static final Log log = Log.lookup(DHtmlUpdateServlet.class); private static final String ATTR_UPDATE_SERVLET = "org.zkoss.zk.au.http.updateServlet"; private static final String ATTR_AU_PROCESSORS = "org.zkoss.zk.au.http.auProcessors"; private long _lastModified; /** (String name, AuExtension). */ private Map _aues = new HashMap(8); private boolean _compress = true; /** * Returns the update servlet of the specified application, or null if not loaded yet. Note: if * the update servlet is not loaded, it returns null. * * @since 3.0.2 */ public static DHtmlUpdateServlet getUpdateServlet(WebApp wapp) { return (DHtmlUpdateServlet) ((ServletContext) wapp.getNativeContext()).getAttribute(ATTR_UPDATE_SERVLET); } // Servlet// public void init() throws ServletException { final ServletConfig config = getServletConfig(); final ServletContext ctx = getServletContext(); ctx.setAttribute(ATTR_UPDATE_SERVLET, this); final WebManager webman = WebManager.getWebManager(ctx); String param = config.getInitParameter("compress"); _compress = param == null || param.length() == 0 || "true".equals(param); if (!_compress) webman.getClassWebResource().setCompress(null); // disable all // Copies au extensions defined before DHtmlUpdateServlet is started final WebApp wapp = webman.getWebApp(); final Map aues = (Map) wapp.getAttribute(ATTR_AU_PROCESSORS); if (aues != null) { for (Iterator it = aues.entrySet().iterator(); it.hasNext(); ) { final Map.Entry me = (Map.Entry) it.next(); addAuExtension((String) me.getKey(), (AuExtension) me.getValue()); } wapp.removeAttribute(ATTR_AU_PROCESSORS); } // ZK 5: extension defined in init-param has the higher priority for (int j = 0; ; ++j) { param = config.getInitParameter("extension" + j); if (param == null) { param = config.getInitParameter("processor" + j); // backward compatible if (param == null) break; } final int k = param.indexOf('='); if (k < 0) { log.warning("Ignore init-param: illegal format, " + param); continue; } final String prefix = param.substring(0, k).trim(); final String clsnm = param.substring(k + 1).trim(); try { addAuExtension(prefix, (AuExtension) Classes.newInstanceByThread(clsnm)); } catch (ClassNotFoundException ex) { log.warning("Ignore init-param: class not found, " + clsnm); } catch (ClassCastException ex) { log.warning("Ignore: " + clsnm + " not implement " + AuExtension.class); } catch (Throwable ex) { log.warning("Ignore init-param: failed to add an AU extension, " + param, ex); } } if (getAuExtension("/upload") == null) { try { addAuExtension("/upload", new AuUploader()); } catch (Throwable ex) { final String msg = "Make sure commons-fileupload.jar is installed."; log.warningBriefly("Failed to configure fileupload. " + msg, ex); // still add /upload to generate exception when fileupload is used addAuExtension( "/upload", new AuExtension() { public void init(DHtmlUpdateServlet servlet) {} public void destroy() {} public void service( HttpServletRequest request, HttpServletResponse response, String pi) throws ServletException, IOException { if (Sessions.getCurrent(false) != null) throw new ServletException("Failed to upload. " + msg); } }); } } if (getAuExtension("/view") == null) addAuExtension("/view", new AuDynaMediar()); } public void destroy() { for (Iterator it = _aues.values().iterator(); it.hasNext(); ) { final AuExtension aue = (AuExtension) it.next(); try { aue.destroy(); } catch (Throwable ex) { log.warningBriefly("Unable to stop " + aue, ex); } } } /* Returns whether to compress the output. * @since 5.0.0 */ public boolean isCompress() { return _compress; } /** * Returns the AU extension that is associated the specified prefix. * * @since 5.0.0 */ public static final AuExtension getAuExtension(WebApp wapp, String prefix) { DHtmlUpdateServlet upsv = DHtmlUpdateServlet.getUpdateServlet(wapp); if (upsv == null) { synchronized (DHtmlUpdateServlet.class) { upsv = DHtmlUpdateServlet.getUpdateServlet(wapp); if (upsv == null) { Map aues = (Map) wapp.getAttribute(ATTR_AU_PROCESSORS); return aues != null ? (AuExtension) aues.get(prefix) : null; } } } return upsv.getAuExtension(prefix); } /** * Adds an AU extension and associates it with the specified prefix, even before {@link * DHtmlUpdateServlet} is started. * * <p>Unlike {@link #addAuExtension(String, AuExtension)}, it can be called even if the update * servlet is not loaded yet ({@link #getUpdateServlet} returns null). * * <p>If there was an AU extension associated with the same name, the the old AU extension will be * replaced. * * @since 5.0.0 */ public static final AuExtension addAuExtension(WebApp wapp, String prefix, AuExtension extension) throws ServletException { DHtmlUpdateServlet upsv = DHtmlUpdateServlet.getUpdateServlet(wapp); if (upsv == null) { synchronized (DHtmlUpdateServlet.class) { upsv = DHtmlUpdateServlet.getUpdateServlet(wapp); if (upsv == null) { checkAuExtension(prefix, extension); Map aues = (Map) wapp.getAttribute(ATTR_AU_PROCESSORS); if (aues == null) wapp.setAttribute(ATTR_AU_PROCESSORS, aues = new HashMap(4)); return (AuExtension) aues.put(prefix, extension); } } } return upsv.addAuExtension(prefix, extension); } /** * Adds an AU extension and associates it with the specified prefix. * * <p>If there was an AU extension associated with the same name, the the old AU extension will be * replaced. * * <p>If you want to add an Au extension, even before DHtmlUpdateServlet is started, use {@link * #addAuExtension(WebApp, String, AuExtension)} instead. * * @param prefix the prefix. It must start with "/", but it cannot be "/" nor "/web" (which are * reserved). * @param extension the AU extension (never null). * @return the previous AU extension associated with the specified prefix, or null if the prefix * was not associated before. * @see #addAuExtension(WebApp,String,AuExtension) * @since 5.0.0 */ public AuExtension addAuExtension(String prefix, AuExtension extension) throws ServletException { checkAuExtension(prefix, extension); if (_aues.get(prefix) == extension) // speed up to avoid sync return extension; // nothing changed extension.init(this); // To avoid using sync in doGet(), we make a copy here final AuExtension old; synchronized (this) { final Map ps = new HashMap(_aues); old = (AuExtension) ps.put(prefix, extension); _aues = ps; } if (old != null) try { old.destroy(); } catch (Throwable ex) { log.warningBriefly("Unable to stop " + old, ex); } return old; } private static void checkAuExtension(String prefix, AuExtension extension) { if (prefix == null || !prefix.startsWith("/") || prefix.length() < 2 || extension == null) throw new IllegalArgumentException(); if (ClassWebResource.PATH_PREFIX.equalsIgnoreCase(prefix)) throw new IllegalArgumentException(ClassWebResource.PATH_PREFIX + " is reserved"); } /** * Returns the AU extension associated with the specified prefix, or null if no AU extension * associated. * * @since 5.0.0 */ public AuExtension getAuExtension(String prefix) { return (AuExtension) _aues.get(prefix); } /** Returns the first AU extension matches the specified path, or null if not found. */ private AuExtension getAuExtensionByPath(String path) { for (Iterator it = _aues.entrySet().iterator(); it.hasNext(); ) { final Map.Entry me = (Map.Entry) it.next(); if (path.startsWith((String) me.getKey())) return (AuExtension) me.getValue(); } return null; } // -- super --// protected long getLastModified(HttpServletRequest request) { final String pi = Https.getThisPathInfo(request); if (pi != null && pi.startsWith(ClassWebResource.PATH_PREFIX) && pi.indexOf('*') < 0 // language independent && !Servlets.isIncluded(request)) { // If a resource extension is registered for the extension, // we assume the content is dynamic final String ext = Servlets.getExtension(pi, false); if (ext == null || getClassWebResource().getExtendlet(ext) == null) { if (_lastModified == 0) _lastModified = new Date().getTime(); // Hard to know when it is modified, so cheat it.. return _lastModified; } } return -1; } private ClassWebResource getClassWebResource() { return WebManager.getWebManager(getServletContext()).getClassWebResource(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { final String pi = Https.getThisPathInfo(request); // if (log.finerable()) log.finer("Path info: "+pi); final ServletContext ctx = getServletContext(); final boolean withpi = pi != null && pi.length() != 0; if (withpi && pi.startsWith(ClassWebResource.PATH_PREFIX)) { // use HttpSession to avoid loading SerializableSession in GAE // and don't retrieve session if possible final ClassWebResource cwr = getClassWebResource(); final HttpSession hsess = shallSession(cwr, pi) ? request.getSession(false) : null; Object oldsess = null; if (hsess == null) { oldsess = SessionsCtrl.getRawCurrent(); SessionsCtrl.setCurrent(new SessionResolverImpl(ctx, request)); // it might be created later } WebApp wapp; Session sess; final Object old = hsess != null ? (wapp = WebManager.getWebAppIfAny(ctx)) != null && (sess = SessionsCtrl.getSession(wapp, hsess)) != null ? I18Ns.setup(sess, request, response, "UTF-8") : I18Ns.setup(hsess, request, response, "UTF-8") : Charsets.setup(null, request, response, "UTF-8"); try { cwr.service(request, response, pi.substring(ClassWebResource.PATH_PREFIX.length())); } finally { if (hsess != null) I18Ns.cleanup(request, old); else { Charsets.cleanup(request, old); SessionsCtrl.setRawCurrent(oldsess); } } return; // done } final Session sess = WebManager.getSession(ctx, request, false); if (withpi) { final AuExtension aue = getAuExtensionByPath(pi); if (aue == null) { response.sendError(response.SC_NOT_FOUND); log.debug("Unknown path info: " + pi); return; } Object oldsess = null; if (sess == null) { oldsess = SessionsCtrl.getRawCurrent(); SessionsCtrl.setCurrent(new SessionResolverImpl(ctx, request)); // it might be created later } final Object old = sess != null ? I18Ns.setup(sess, request, response, "UTF-8") : Charsets.setup(null, request, response, "UTF-8"); try { aue.service(request, response, pi); } finally { if (sess != null) I18Ns.cleanup(request, old); else { Charsets.cleanup(request, old); SessionsCtrl.setRawCurrent(oldsess); } } return; // done } // AU if (sess == null) { response.setIntHeader("ZK-Error", response.SC_GONE); // denote timeout // Bug 1849088: rmDesktop might be sent after invalidate // Bug 1859776: need send response to client for redirect or others final WebApp wapp = WebManager.getWebAppIfAny(ctx); final String dtid = getAuDecoder(wapp).getDesktopId(request); if (dtid != null) sessionTimeout(request, response, wapp, dtid); return; } // Feature 3285074 add no-cache for security risk. response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-cache"); response.setHeader("Cache-Control", "no-store"); response.setHeader("Expires", "-1"); final Object old = I18Ns.setup(sess, request, response, "UTF-8"); try { process(sess, request, response); } finally { I18Ns.cleanup(request, old); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } private static boolean shallSession(ClassWebResource cwr, String pi) { return cwr.getExtendlet(Servlets.getExtension(pi, false)) != null || (pi != null && pi.indexOf('*') >= 0); // Optimize the access of static resources (for GAE) } // -- ASYNC-UPDATE --// /** * Process asynchronous update requests from the client. * * @since 3.0.0 */ protected void process(Session sess, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { final String errClient = request.getHeader("ZK-Error-Report"); if (errClient != null) if (log.debugable()) log.debug("Error found at client: " + errClient + "\n" + Servlets.getDetail(request)); // parse desktop ID final WebApp wapp = sess.getWebApp(); final WebAppCtrl wappc = (WebAppCtrl) wapp; final AuDecoder audec = getAuDecoder(wapp); final String dtid = audec.getDesktopId(request); if (dtid == null) { // Bug 1929139: incomplete request (IE only) if (log.debugable()) { final String msg = "Incomplete request\n" + Servlets.getDetail(request); log.debug(msg); } response.sendError(467, "Incomplete request"); return; } Desktop desktop = getDesktop(sess, dtid); if (desktop == null) { final String cmdId = audec.getFirstCommand(request); if (!"rmDesktop".equals(cmdId)) desktop = recoverDesktop(sess, request, response, wappc, dtid); if (desktop == null) { response.setIntHeader("ZK-Error", response.SC_GONE); // denote timeout sessionTimeout(request, response, wapp, dtid); return; } } WebManager.setDesktop(request, desktop); // reason: a new page might be created (such as include) final String sid = request.getHeader("ZK-SID"); if (sid != null) // Some client might not have ZK-SID response.setHeader("ZK-SID", sid); // parse commands final Configuration config = wapp.getConfiguration(); final List aureqs; boolean keepAlive = false; try { final boolean timerKeepAlive = config.isTimerKeepAlive(); aureqs = audec.decode(request, desktop); for (Iterator it = aureqs.iterator(); it.hasNext(); ) { final String cmdId = ((AuRequest) it.next()).getCommand(); keepAlive = !(!timerKeepAlive && Events.ON_TIMER.equals(cmdId)) && !"dummy".equals(cmdId); // dummy is used for PollingServerPush for piggyback if (keepAlive) break; // done } } catch (Throwable ex) { log.warningBriefly(ex); responseError(request, response, Exceptions.getMessage(ex)); return; } if (aureqs.isEmpty()) { final String errmsg = "Illegal request: cmd required"; log.debug(errmsg); responseError(request, response, errmsg); return; } ((SessionCtrl) sess).notifyClientRequest(keepAlive); // if (log.debugable()) log.debug("AU request: "+aureqs); final DesktopCtrl desktopCtrl = (DesktopCtrl) desktop; final Execution exec = new ExecutionImpl(getServletContext(), request, response, desktop, null); if (sid != null) ((ExecutionCtrl) exec).setRequestId(sid); final AuWriter out = AuWriters.newInstance(); out.setCompress(_compress); out.open( request, response, desktop.getDevice().isSupported(Device.RESEND) ? getProcessTimeout(config.getResendDelay()) : 0); // Note: getResendDelay() might return nonpositive try { wappc.getUiEngine().execUpdate(exec, aureqs, out); } catch (RequestOutOfSequenceException ex) { log.warning(ex.getMessage()); response.setHeader("ZK-SID", sid); response.setIntHeader("ZK-Error", AuResponse.SC_OUT_OF_SEQUENCE); } out.close(request, response); } /** * Returns the desktop of the specified ID, or null if not found. If null is returned, {@link * #recoverDesktop} will be invoked. * * @param sess the session (never null) * @param dtid the desktop ID to look for * @since 5.0.3 */ protected Desktop getDesktop(Session sess, String dtid) { return ((WebAppCtrl) sess.getWebApp()).getDesktopCache(sess).getDesktopIfAny(dtid); } private static int getProcessTimeout(int resendDelay) { if (resendDelay > 0) { resendDelay = (resendDelay * 3) >> 2; if (resendDelay <= 0) resendDelay = 1; } return resendDelay; } /** @param wapp the Web application (or null if not available yet) */ private void sessionTimeout( HttpServletRequest request, HttpServletResponse response, WebApp wapp, String dtid) throws ServletException, IOException { final String sid = request.getHeader("ZK-SID"); if (sid != null) response.setHeader("ZK-SID", sid); final AuWriter out = AuWriters.newInstance().open(request, response, 0); if (!getAuDecoder(wapp).isIgnorable(request, wapp)) { final String deviceType = getDeviceType(request); URIInfo ui = wapp != null ? (URIInfo) wapp.getConfiguration().getTimeoutURI(deviceType) : null; String uri = ui != null ? ui.uri : null; out.write(new AuConfirmClose(null)); // Bug: B50-3147382 final AuResponse resp; if (uri != null) { if (uri.length() != 0) uri = Encodes.encodeURL(getServletContext(), request, response, uri); resp = new AuSendRedirect(uri, null); } else { String msg = wapp.getConfiguration().getTimeoutMessage(deviceType); if (msg != null && msg.startsWith("label:")) { final String key; msg = Labels.getLabel(key = msg.substring(6), new Object[] {dtid}); if (msg == null) log.warning("Label not found, " + key); } if (msg == null) msg = Messages.get(MZk.UPDATE_OBSOLETE_PAGE, dtid); resp = new AuObsolete(dtid, msg); } out.write(resp); } out.close(request, response); } private static String getDeviceType(HttpServletRequest request) { final String agt = request.getHeader("user-agent"); if (agt != null && agt.length() > 0) { try { return Devices.getDeviceByClient(agt).getType(); } catch (Throwable ex) { log.warning("Unknown device for " + agt); } } return "ajax"; } /** * Recovers the desktop if possible. It is called if {@link #getDesktop} returns null. * * <p>The default implementation will look for any failover manager ({@link FailoverManager}) is * registered, and forward the invocation to it if found. * * @return the recovered desktop, or null if failed to recover * @since 5.0.3 */ protected Desktop recoverDesktop( Session sess, HttpServletRequest request, HttpServletResponse response, WebAppCtrl wappc, String dtid) { final FailoverManager failover = wappc.getFailoverManager(); if (failover != null) { Desktop desktop = null; final ServletContext ctx = getServletContext(); try { if (failover.isRecoverable(sess, dtid)) { desktop = WebManager.getWebManager(ctx).getDesktop(sess, request, response, null, true); if (desktop == null) // forward or redirect throw new IllegalStateException("sendRediect or forward not allowed in recovering"); wappc .getUiEngine() .execRecover(new ExecutionImpl(ctx, request, response, desktop, null), failover); return desktop; // success } } catch (Throwable ex) { log.error("Unable to recover " + dtid, ex); if (desktop != null) ((DesktopCtrl) desktop).recoverDidFail(ex); } } return null; } /** Generates a response for an error message. */ private static void responseError( HttpServletRequest request, HttpServletResponse response, String errmsg) throws IOException { // Don't use sendError because Browser cannot handle UTF-8 AuWriter out = AuWriters.newInstance().open(request, response, 0); out.write(new AuAlert(errmsg)); out.close(request, response); } private static final AuDecoder getAuDecoder(WebApp wapp) { AuDecoder audec = wapp != null ? ((WebAppCtrl) wapp).getAuDecoder() : null; return audec != null ? audec : _audec; } private static final AuDecoder _audec = new AuDecoder() { public String getDesktopId(Object request) { return ((HttpServletRequest) request).getParameter("dtid"); } public String getFirstCommand(Object request) { return ((HttpServletRequest) request).getParameter("cmd.0"); } public List decode(Object request, Desktop desktop) { final List aureqs = new LinkedList(); final HttpServletRequest hreq = (HttpServletRequest) request; for (int j = 0; ; ++j) { final String cmdId = hreq.getParameter("cmd_" + j); if (cmdId == null) break; final String uuid = hreq.getParameter("uuid_" + j); final String data = hreq.getParameter("data_" + j); final Map decdata = (Map) JSONValue.parse(data); aureqs.add( uuid == null || uuid.length() == 0 ? new AuRequest(desktop, cmdId, decdata) : new AuRequest(desktop, uuid, cmdId, decdata)); } return aureqs; } public boolean isIgnorable(Object request, WebApp wapp) { final HttpServletRequest hreq = (HttpServletRequest) request; for (int j = 0; ; ++j) { if (hreq.getParameter("cmd_" + j) == null) break; final String opt = hreq.getParameter("opt_" + j); if (opt == null || opt.indexOf("i") < 0) return false; // not ignorable } return true; } }; }
/** * To help invoke a method with {@link BindingParam} etc.. features. * * @author dennis * @since 6.0.0 */ public class ParamCall { private static final Log _log = Log.lookup(ParamCall.class); private Map<Class<? extends Annotation>, ParamResolver<Annotation>> _paramResolvers; private List<Type> _types; // to map class type directly, regardless the annotation private boolean _mappingType; // to enable the map class type without annotation, it is for compatible to rc2, // only support BindeContext and Binder private ContextObjects _contextObjects; private static final String COOKIE_CACHE = "$PARAM_COOKIES$"; private Component _root = null; private Component _component = null; private Execution _execution = null; private Binder _binder = null; private BindContext _bindContext = null; public ParamCall() { this(true); } public ParamCall(boolean mappingType) { _paramResolvers = new HashMap<Class<? extends Annotation>, ParamResolver<Annotation>>(); _contextObjects = new ContextObjects(); _types = new ArrayList<Type>(); _mappingType = mappingType; _paramResolvers.put( ContextParam.class, new ParamResolver<Annotation>() { public Object resolveParameter(Annotation anno, Class<?> returnType) { Object val = _contextObjects.get(((ContextParam) anno).value()); return val == null ? null : Classes.coerce(returnType, val); } }); } public void setBindContext(BindContext ctx) { _bindContext = ctx; _types.add(new Type(ctx.getClass(), _bindContext)); } public void setBinder(Binder binder) { _binder = binder; _types.add(new Type(binder.getClass(), _binder)); _root = binder.getView(); } public void setBindingArgs(final Map<String, Object> bindingArgs) { _paramResolvers.put( BindingParam.class, new ParamResolver<Annotation>() { public Object resolveParameter(Annotation anno, Class<?> returnType) { Object val = bindingArgs.get(((BindingParam) anno).value()); return val == null ? null : Classes.coerce(returnType, val); } }); } public void call(Object base, Method method) { Class<?>[] paramTypes = method.getParameterTypes(); java.lang.annotation.Annotation[][] parmAnnos = method.getParameterAnnotations(); Object[] params = new Object[paramTypes.length]; try { for (int i = 0; i < paramTypes.length; i++) { params[i] = resolveParameter(parmAnnos[i], paramTypes[i]); } method.invoke(base, params); } catch (InvocationTargetException invokEx) { // Ian YT Tsai (2012.06.20), while InvocationTargetException, // using original exception is much meaningful. Throwable c = invokEx.getCause(); if (c == null) c = invokEx; _log.error(c); throw UiException.Aide.wrap(c); } catch (Exception e) { _log.error(e); throw UiException.Aide.wrap(e); } } private Object resolveParameter(java.lang.annotation.Annotation[] parmAnnos, Class<?> paramType) { Object val = null; boolean hitResolver = false; Default defAnno = null; for (Annotation anno : parmAnnos) { Class<?> annotype = anno.annotationType(); if (defAnno == null && annotype.equals(Default.class)) { defAnno = (Default) anno; continue; } ParamResolver<Annotation> resolver = _paramResolvers.get(annotype); if (resolver == null) continue; hitResolver = true; val = resolver.resolveParameter(anno, paramType); if (val != null) { break; } // don't break until get a value } if (val == null && defAnno != null) { val = Classes.coerce(paramType, defAnno.value()); } // to compatible to rc2, do we have to? if (_mappingType && val == null && !hitResolver && _types != null) { for (Type type : _types) { if (type != null && paramType.isAssignableFrom(type.clz)) { val = type.value; break; } } } return val; } // utility to hold implicit class and runtime value private static class Type { final Class<?> clz; final Object value; public Type(Class<?> clz, Object value) { this.clz = clz; this.value = value; } } private interface ParamResolver<T> { public Object resolveParameter(T anno, Class<?> returnType); } public void setComponent(Component comp) { _component = comp; // scope param _paramResolvers.put( ScopeParam.class, new ParamResolver<Annotation>() { public Object resolveParameter(Annotation anno, Class<?> returnType) { final String name = ((ScopeParam) anno).value(); final Scope[] ss = ((ScopeParam) anno).scopes(); Object val = null; for (Scope s : ss) { switch (s) { case AUTO: if (ss.length == 1) { val = _component.getAttribute(name, true); } else { throw new UiException( "don't use " + s + " with other scopes " + Arrays.toString(ss)); } } } if (val == null) { for (Scope scope : ss) { final String scopeName = scope.getName(); Object scopeObj = Components.getImplicit(_component, scopeName); if (scopeObj instanceof Map) { val = ((Map<?, ?>) scopeObj).get(name); if (val != null) break; } else if (scopeObj != null) { _log.error("the scope of " + scopeName + " is not a Map, is " + scopeObj); } } } // zk-1469, if (val instanceof ReferenceBinding) { val = resolveReferenceBinding(name, (ReferenceBinding) val, returnType); } return val == null ? null : Classes.coerce(returnType, val); } }); // component _paramResolvers.put( SelectorParam.class, new ParamResolver<Annotation>() { public Object resolveParameter(Annotation anno, Class<?> returnType) { final String selector = ((SelectorParam) anno).value(); final List<Component> result = Selectors.find(_root, selector); Object val; if (!Collection.class.isAssignableFrom(returnType)) { val = result.size() > 0 ? Classes.coerce(returnType, result.get(0)) : null; } else { val = Classes.coerce(returnType, result); } return val; } }); } private Object resolveReferenceBinding( String name, ReferenceBinding rbinding, Class<?> returnType) { BindEvaluatorX evalx = rbinding.getBinder().getEvaluatorX(); // resolve by name or by rbinding.propertyString directly? Object val = BindEvaluatorXUtil.eval(evalx, rbinding.getComponent(), name, returnType, null); // following is quick but not safe because of the null arg // val = ((ReferenceBinding)val).getValue(null); return val; } public void setExecution(Execution exec) { _execution = exec; // http param _paramResolvers.put( QueryParam.class, new ParamResolver<Annotation>() { public Object resolveParameter(Annotation anno, Class<?> returnType) { Object val = _execution.getParameter(((QueryParam) anno).value()); return val == null ? null : Classes.coerce(returnType, val); } }); _paramResolvers.put( HeaderParam.class, new ParamResolver<Annotation>() { public Object resolveParameter(Annotation anno, Class<?> returnType) { Object val = _execution.getHeader(((HeaderParam) anno).value()); return val == null ? null : Classes.coerce(returnType, val); } }); _paramResolvers.put( CookieParam.class, new ParamResolver<Annotation>() { @SuppressWarnings("unchecked") public Object resolveParameter(Annotation anno, Class<?> returnType) { Map<String, Object> m = (Map<String, Object>) _execution.getAttribute(COOKIE_CACHE); if (m == null) { final Object req = _execution.getNativeRequest(); m = new HashMap<String, Object>(); _execution.setAttribute(COOKIE_CACHE, m); if (req instanceof HttpServletRequest) { final Cookie[] cks = ((HttpServletRequest) req).getCookies(); if (cks != null) { for (Cookie ck : cks) { m.put(ck.getName().toLowerCase(), ck.getValue()); } } } else /* if(req instanceof PortletRequest)*/ { // no cookie in protlet 1.0 } } Object val = m == null ? null : m.get(((CookieParam) anno).value().toLowerCase()); return val == null ? null : Classes.coerce(returnType, val); } }); // execution _paramResolvers.put( ExecutionParam.class, new ParamResolver<Annotation>() { public Object resolveParameter(Annotation anno, Class<?> returnType) { Object val = _execution.getAttribute(((ExecutionParam) anno).value()); return val == null ? null : Classes.coerce(returnType, val); } }); _paramResolvers.put( ExecutionArgParam.class, new ParamResolver<Annotation>() { public Object resolveParameter(Annotation anno, Class<?> returnType) { Object val = _execution.getArg().get(((ExecutionArgParam) anno).value()); return val == null ? null : Classes.coerce(returnType, val); } }); } class ContextObjects { public Object get(ContextType type) { switch (type) { // bind contexts case BIND_CONTEXT: return _bindContext; case BINDER: return _binder; case COMMAND_NAME: return _bindContext == null ? null : _bindContext.getCommandName(); case TRIGGER_EVENT: return _bindContext == null ? null : _bindContext.getTriggerEvent(); // zk execution contexts case EXECUTION: return _execution; case COMPONENT: return _component; case SPACE_OWNER: return _component == null ? null : _component.getSpaceOwner(); case VIEW: return _binder == null ? null : _binder.getView(); case PAGE: return _component == null ? null : _component.getPage(); case DESKTOP: return _component == null ? null : _component.getDesktop(); case SESSION: return _component == null ? null : Components.getImplicit(_component, "session"); case APPLICATION: return _component == null ? null : Components.getImplicit(_component, "application"); } return null; } } }
/** * Functions to manipulate strings in EL. * * @author tomyeh */ public class StringFns { private static Log log = Log.lookup(StringFns.class); /** Catenates two strings. Note: null is considered as empty. */ public static String cat(String s1, String s2) { if (s1 == null) return s2 != null ? s2 : ""; return s2 != null ? s1 + s2 : s1; } /** Catenates three strings. Note: null is considered as empty. */ public static String cat3(String s1, String s2, String s3) { return cat(cat(s1, s2), s3); } /** Catenates four strings. Note: null is considered as empty. */ public static String cat4(String s1, String s2, String s3, String s4) { return cat(cat(cat(s1, s2), s3), s4); } /** Catenates four strings. Note: null is considered as empty. */ public static String cat5(String s1, String s2, String s3, String s4, String s5) { return cat(cat(cat(cat(s1, s2), s3), s4), s5); } /** * Converts all of the characters in this String to upper case using the rules of the current * Locale. * * @see Locales#getCurrent * @since 5.0.7 */ public static String toLowerCase(String s) { return s != null ? s.toLowerCase(Locales.getCurrent()) : null; } /** * Converts all of the characters in this String to upper case using the rules of the current * Locale. * * @see Locales#getCurrent * @since 5.0.7 */ public static String toUpperCase(String s) { return s != null ? s.toUpperCase(Locales.getCurrent()) : null; } /** * Returns a copy of the string, with leading and trailing whitespace omitted. * * @since 5.0.7 */ public static String trim(String s) { return s != null ? s.trim() : null; } /** * Splits a string. * * @since 5.0.7 */ public static String[] split(String s, String separator) { return s != null ? s.split(separator) : null; } /** Joins an array of string. since 5.0.7 */ public static String join(Object[] ss, String separator) { if (ss == null) return null; final StringBuffer sb = new StringBuffer(); for (int j = 0; j < ss.length; ++j) { if (j != 0) sb.append(separator); sb.append(ss[j]); } return ss.toString(); } /** * Tests if this string starts with the specified prefix. * * @since 5.0.7 */ public static boolean startsWith(String s1, String s2) { return s1 != null && s2 != null && s1.startsWith(s2); } /** * Tests if this string ends with the specified suffix. * * @since 5.0.7 */ public static boolean endsWith(String s1, String s2) { return s1 != null && s2 != null && s1.endsWith(s2); } /** * Returns a new string that is a substring of this string. * * @since 5.0.7 */ public static String substring(String s, int from, int to) { return s != null ? s.substring(from, to) : null; } /** Replaces all occurrances of 'from' in 'src' with 'to' */ public static String replace(String src, String from, String to) { if (Objects.equals(from, to)) return src; final StringBuffer sb = new StringBuffer(src); if ("\n".equals(from) || "\r\n".equals(from)) { replace0(sb, "\r\n", to); replace0(sb, "\n", to); } else { replace0(sb, from, to); } return sb.toString(); } private static void replace0(StringBuffer sb, String from, String to) { final int len = from.length(); for (int j = 0; (j = sb.indexOf(from, j)) >= 0; ) { sb.replace(j, j += len, to); } } /** * Eliminates single and double quotations to avoid JavaScript injection. It eliminates all * quotations. In other words, the specified string shall NOT contain any quotations. * * <p>It is used to avoid JavaScript injection. For exmple, in DSP or JSP pages, the following * codes is better to escape with this method. <code><input value="${c:eatQuot(param.some)}"/> * </code> * * @since 3.5.2 */ public static String eatQuot(String s) { final int len = s != null ? s.length() : 0; StringBuffer sb = null; for (int j = 0; j < len; ++j) { final char cc = s.charAt(j); if (cc == '\'' || cc == '"') { if (sb == null) { log.warning("JavaScript Injection? Unexpected string detected: " + s); sb = new StringBuffer(len); if (j > 0) sb.append(s.substring(0, j)); } } else if (sb != null) sb.append(cc); } return sb != null ? sb.toString() : s; } }
/** * A window. * * <p>Unlike other elements, each {@link Window} is an independent ID space (by implementing {@link * org.zkoss.zk.ui.IdSpace}). It means a window and all its descendants forms a ID space and the ID * of each of them is unique in this space. You could retrieve any of them in this space by calling * {@link #getFellow}. * * <p>If a window X is a descendant of another window Y, X's descendants are not visible in Y's * space. To retrieve a descendant, say Z, of X, you have to invoke Y.getFellow('X').getFellow('Z'). * * <p>Events:<br> * onMove, onOpen, onMaximize, onMinimize, and onClose.<br> * Note: to have better performance, onOpen is sent only if a non-deferrable event listener is * registered (see {@link org.zkoss.zk.ui.event.Deferrable}). * * <p><code>onMaximize</code> and <code>onMinimize</code> are supported. (since 3.5.0) * * <p><code>onClose</code> is sent when the close button is pressed (if {@link #isClosable} is * true). The window has to detach or hide the window. By default, {@link #onClose} detaches the * window. To prevent it from detached, you have to call {@link * org.zkoss.zk.ui.event.Event#stopPropagation} to prevent {@link #onClose} is called. * * <p>On the other hand, <code>onOpen</code> is sent when a popup window (i.e., {@link #getMode} is * popup) is closed due to user's activity (such as press ESC). This event is only a notification. * In other words, the popup is hidden before the event is sent to the server. The application * cannot prevent the window from being hidden. * * <p>Default {@link #getZclass}: z-window-{@link #getMode()}.(since 3.5.0) * * @author tomyeh */ public class Window extends XulElement implements org.zkoss.zul.api.Window, Framable, IdSpace { private static final Log log = Log.lookup(Window.class); private static final long serialVersionUID = 20100721L; private transient Caption _caption; private String _border = "none"; private String _title = ""; /** One of MODAL, _MODAL_, EMBEDDED, OVERLAPPED, HIGHLIGHTED, POPUP. */ private int _mode = EMBEDDED; /** Used for doModal. */ private Mutex _mutex = new Mutex(); /** The style used for the content block. */ private String _cntStyle; /** The style class used for the content block. */ private String _cntSclass; /** How to position the window. */ private String _pos; /** Whether to show a close button. */ private boolean _closable; /** Whether the window is sizable. */ private boolean _sizable; /** Whether to show the shadow. */ private boolean _shadow = true; private boolean _maximizable, _minimizable, _maximized, _minimized; private int _minheight = 100, _minwidth = 200; /** Embeds the window as normal component. */ public static final int EMBEDDED = 0; /** * Makes the window as a modal dialog. once {@link #doModal} is called, the execution of the event * processing thread is suspended until one of the following occurs. * * <ol> * <li>{@link #setMode} is called with a mode other than MODAL. * <li>Either {@link #doOverlapped}, {@link #doPopup}, {@link #doEmbedded}, or {@link * #doHighlighted} is called. * <li>{@link #setVisible} is called with false. * <li>The window is detached from the window. * </ol> * * <p>Note: In the last two cases, the mode becomes {@link #OVERLAPPED}. In other words, one might * say a modal window is a special overlapped window. * * @see #HIGHLIGHTED */ public static final int MODAL = 1; // Represent a modal when the event thread is disabled (internal) private static final int _MODAL_ = -100; /** Makes the window as overlapped other components. */ public static final int OVERLAPPED = 2; /** * Makes the window as popup. It is similar to {@link #OVERLAPPED}, except it is auto hidden when * user clicks outside of the window. */ public static final int POPUP = 3; /** * Makes the window as highlighted. Its visual effect is the same as {@link #MODAL}. However, from * the server side's viewpoint, it is similar to {@link #OVERLAPPED}. The execution won't be * suspended when {@link #doHighlighted} is called. * * @see #MODAL * @see #OVERLAPPED */ public static final int HIGHLIGHTED = 4; static { addClientEvent(Window.class, Events.ON_CLOSE, 0); addClientEvent(Window.class, Events.ON_MOVE, CE_DUPLICATE_IGNORE | CE_IMPORTANT); addClientEvent(Window.class, Events.ON_SIZE, CE_DUPLICATE_IGNORE | CE_IMPORTANT); addClientEvent(Window.class, Events.ON_OPEN, CE_IMPORTANT); addClientEvent(Window.class, Events.ON_Z_INDEX, CE_DUPLICATE_IGNORE | CE_IMPORTANT); addClientEvent(Window.class, Events.ON_MAXIMIZE, CE_DUPLICATE_IGNORE | CE_IMPORTANT); addClientEvent(Window.class, Events.ON_MINIMIZE, CE_DUPLICATE_IGNORE | CE_IMPORTANT); } public Window() { setAttribute( "z$is", Boolean .TRUE); // optional but optimized to mean no need to generate z$is since client handles // it } /** * @param title the window title (see {@link #setTitle}). * @param border the border (see {@link #setBorder}). * @param closable whether it is closable (see {@link #setClosable}). */ public Window(String title, String border, boolean closable) { this(); setTitle(title); setBorder(border); setClosable(closable); } /** * Returns whether the window is maximized. * * @since 3.5.0 */ public boolean isMaximized() { return _maximized; } /** * Sets whether the window is maximized, and then the size of the window will depend on it to show * a appropriate size. In other words, if true, the size of the window will count on the size of * its offset parent node whose position is absolute (by not {@link #inEmbedded()}) or its parent * node. Otherwise, its size will be original size. Note that the maximized effect will run at * client's sizing phase not initial phase. * * <p>Default: false. * * @exception UiException if {@link #isMaximizable} is false. * @since 3.5.0 */ public void setMaximized(boolean maximized) { if (_maximized != maximized) { if (!_maximizable) throw new UiException("Not maximizable, " + this); _maximized = maximized; if (_maximized) { _minimized = false; setVisible0(true); // avoid dead loop } smartUpdate("maximized", _maximized); } } /** * Returns whether to display the maximizing button and allow the user to maximize the window. * * <p>Default: false. * * @since 3.5.0 */ public boolean isMaximizable() { return _maximizable; } /** * Sets whether to display the maximizing button and allow the user to maximize the window, when a * window is maximized, the button will automatically change to a restore button with the * appropriate behavior already built-in that will restore the window to its previous size. * * <p>Default: false. * * <p>Note: the maximize button won't be displayed if no title or caption at all. * * @since 3.5.0 */ public void setMaximizable(boolean maximizable) { if (_maximizable != maximizable) { _maximizable = maximizable; smartUpdate("maximizable", _maximizable); } } /** * Returns whether the window is minimized. * * <p>Default: false. * * @since 3.5.0 */ public boolean isMinimized() { return _minimized; } /** * Sets whether the window is minimized. * * <p>Default: false. * * @exception UiException if {@link #isMinimizable} is false. * @since 3.5.0 */ public void setMinimized(boolean minimized) { if (_minimized != minimized) { if (!_minimizable) throw new UiException("not minimizable, " + this); _minimized = minimized; if (_minimized) { _maximized = false; setVisible0(false); // avoid dead loop } else setVisible0(true); smartUpdate("minimized", _minimized); } } /** * Returns whether to display the minimizing button and allow the user to minimize the window. * * <p>Default: false. * * @since 3.5.0 */ public boolean isMinimizable() { return _minimizable; } /** * Sets whether to display the minimizing button and allow the user to minimize the window. Note * that this button provides no implementation -- the behavior of minimizing a window is * implementation-specific, so the MinimizeEvent event must be handled and a custom minimize * behavior implemented for this option to be useful. * * <p>Default: false. * * <p>Note: the maximize button won't be displayed if no title or caption at all. * * @see MinimizeEvent * @since 3.5.0 */ public void setMinimizable(boolean minimizable) { if (_minimizable != minimizable) { _minimizable = minimizable; smartUpdate("minimizable", _minimizable); } } /** * Sets the minimum height in pixels allowed for this window. If negative, 100 is assumed. * * <p>Default: 100. * * <p>Note: Only applies when {@link #isSizable()} = true. * * @since 3.5.0 */ public void setMinheight(int minheight) { if (minheight < 0) minheight = 100; if (_minheight != minheight) { _minheight = minheight; smartUpdate("minheight", _minheight); } } /** * Returns the minimum height. * * <p>Default: 100. * * @since 3.5.0 */ public int getMinheight() { return _minheight; } /** * Sets the minimum width in pixels allowed for this window. If negative, 200 is assumed. * * <p>Default: 200. * * <p>Note: Only applies when {@link #isSizable()} = true. * * @since 3.5.0 */ public void setMinwidth(int minwidth) { if (minwidth < 0) minwidth = 200; if (_minwidth != minwidth) { _minwidth = minwidth; smartUpdate("minwidth", _minwidth); } } /** * Returns the minimum width. * * <p>Default: 200. * * @since 3.5.0 */ public int getMinwidth() { return _minwidth; } /** * @deprecated As release of 5.0.0, replaced with {@link * org.zkoss.zk.ui.HtmlBasedComponent#setAction}. */ public static void setDefaultActionOnShow(String onshow) {} /** * @deprecated As release of 5.0.0, replaced with {@link * org.zkoss.zk.ui.HtmlBasedComponent#setAction}. */ public static String getDefaultActionOnShow() { return null; } /** Returns the caption of this window. */ public Caption getCaption() { return _caption; } /** * Returns the caption of this window. * * @since 3.5.2 */ public org.zkoss.zul.api.Caption getCaptionApi() { return getCaption(); } /** * Returns the border. * * <p>Default: "none". */ public String getBorder() { return _border; } /** * Sets the border (either none or normal). * * @param border the border. If null, "0" or "false", "none" is assumed. If "true", "normal" is * assumed (since 5.0.8). */ public void setBorder(String border) { if (border == null || "0".equals(border) || "false".equals(border)) border = "none"; else if ("true".equals(border)) border = "normal"; if (!Objects.equals(_border, border)) { _border = border; smartUpdate("border", border); } } /** * Enables or disables the border. * * @param border whether to have a border. If true is specified, it is the same as <code> * setBorder("normal")</code>. * @since 5.0.8 */ public void setBorder(boolean border) { setBorder(border ? "normal" : "none"); } /** * Returns the title. Besides this attribute, you could use {@link Caption} to define a more * sophiscated caption (aka., title). * * <p>If a window has a caption whose label ({@link Caption#getLabel}) is not empty, then this * attribute is ignored. * * <p>Default: empty. */ public String getTitle() { return _title; } /** Sets the title. */ public void setTitle(String title) { if (title == null) title = ""; if (!Objects.equals(_title, title)) { _title = title; smartUpdate("title", title); } } /** * Returns the current mode. One of "modal", "embedded", "overlapped", "popup", and "highlighted". */ public String getMode() { return modeToString(_mode); } private static String modeToString(int mode) { switch (mode) { case MODAL: case _MODAL_: return "modal"; case POPUP: return "popup"; case OVERLAPPED: return "overlapped"; case HIGHLIGHTED: return "highlighted"; default: return "embedded"; } } /** * Sets the mode to overlapped, popup, modal, embedded or highlighted. * * <p>Notice: {@link Events#ON_MODAL} is posted if you specify "modal" to this method. Unlike * {@link #doModal}, {@link Events#ON_MODAL} is posted, so the window will become modal later * (since 3.0.4). In other words, setMode("modal") never suspends the execution of the current * thread. On the other hand, {@link #doModal} will suspends the execution if executed in an event * listener, or throws an exception if <em>not</em> executed in an event listener. * * <p>Refer to <a * href="http://books.zkoss.org/wiki/ZK_Component_Reference/Containers/Window">Overlapped, Popup, * Modal, Highlighted and Embedded</a> for more information. * * <p>If the event processing thread is disabled (it is the default), InterruptedException won't * be thrown. * * @param name the mode which could be one of "embedded", "overlapped", "popup", "modal", * "highlighted". Note: it cannot be "modal". Use {@link #doModal} instead. * @exception InterruptedException thrown if "modal" is specified the event thread is enabled * (disabled by default), and one of the following conditions occurs: 1) the desktop or the * Web application is being destroyed, or 2) {@link * org.zkoss.zk.ui.sys.DesktopCtrl#ceaseSuspendedThread}. To tell the difference, check the * getMessage method of InterruptedException. */ public void setMode(String name) throws InterruptedException { if ("popup".equals(name)) doPopup(); else if ("overlapped".equals(name)) doOverlapped(); else if ("embedded".equals(name)) doEmbedded(); else if ("modal".equals(name)) { if (isEventThreadEnabled(false)) Events.postEvent(Events.ON_MODAL, this, null); else doModal(); } else if ("highlighted".equals(name)) doHighlighted(); else throw new WrongValueException("Unknown mode: " + name); } /** * Sets the mode to overlapped, popup, modal, embedded or highlighted. * * @see #setMode(String) */ public void setMode(int mode) throws InterruptedException { switch (mode) { case POPUP: doPopup(); break; case OVERLAPPED: doOverlapped(); break; case EMBEDDED: doEmbedded(); break; case MODAL: if (isEventThreadEnabled(false)) Events.postEvent(Events.ON_MODAL, this, null); else doModal(); break; case HIGHLIGHTED: doHighlighted(); break; default: throw new WrongValueException("Unknown mode: " + mode); } } /** Returns whether this is a modal dialog. */ public boolean inModal() { return _mode == MODAL || _mode == _MODAL_; } /** * Returns whether this is embedded with other components (Default). * * @see #doEmbedded */ public boolean inEmbedded() { return _mode == EMBEDDED; } /** Returns whether this is a overlapped window. */ public boolean inOverlapped() { return _mode == OVERLAPPED; } /** Returns whether this is a popup window. */ public boolean inPopup() { return _mode == POPUP; } /** Returns whether this is a highlighted window. */ public boolean inHighlighted() { return _mode == HIGHLIGHTED; } /** * Makes this window as a modal dialog. It will automatically center the window (ignoring {@link * #getLeft} and {@link #getTop}). * * <p>Notice: though both setMode("modal") and doModal() both causes the window to become modal, * they are a bit different. doModal causes the event listener to suspend immediately, while * setMode("modal") posts an event ({@link Events#ON_MODAL}). That is, {@link #setMode} won't * suspend the execution immediately, but {@link #doModal} will. {@link #doModal} can be called * only in an event listener, while {@link #setMode} can be called anytime. * * @exception SuspendNotAllowedException if 1) not in an event listener;<br> * 2) the event thread is disabled.<br> * 3) there are too many suspended processing thread than the deployer allows. By default, * there is no limit of # of suspended threads. * @exception InterruptedException thrown if the desktop or the Web application is being * destroyed, or {@link org.zkoss.zk.ui.sys.DesktopCtrl#ceaseSuspendedThread}. To tell the * difference, check the getMessage method of InterruptedException. * @since 3.0.4 */ public void doModal() throws InterruptedException, SuspendNotAllowedException { if (!isEventThreadEnabled(true)) { checkOverlappable(_MODAL_); setNonModalMode(_MODAL_); return; } checkOverlappable(MODAL); if (_mode != MODAL) { if (!Events.inEventListener()) throw new SuspendNotAllowedException("doModal must be called in an event listener"); int oldmode = _mode; boolean oldvisi = isVisible(); setVisible(true); // if MODAL, it must be visible; vice versa try { enterModal(); } catch (SuspendNotAllowedException ex) { handleFailedModal(oldmode, oldvisi); throw ex; } } } private void handleFailedModal(int oldmode, boolean oldvisi) { try { if (Executions.getCurrent().getAttribute("javax.servlet.error.exception") != null) { // handle it specially if it is used for dispalying err setMode(HIGHLIGHTED); } else { setMode(oldmode); // restore setVisible(oldvisi); } } catch (Throwable ex) { log.realCauseBriefly("Causing another error", ex); } } /** Makes this window as overlapped with other components. */ public void doOverlapped() { checkOverlappable(OVERLAPPED); setNonModalMode(OVERLAPPED); } /** * Makes this window as popup, which is overlapped with other component and auto-hiden when user * clicks outside of the window. */ public void doPopup() { checkOverlappable(POPUP); setNonModalMode(POPUP); } /** * Makes this window as highlited. The visual effect is the similar to the modal window, but, like * overlapped, it doesn't suspend (block) the execution at the server. In other words, it is more * like an overlapped window from the server side's viewpoint. */ public void doHighlighted() { checkOverlappable(HIGHLIGHTED); setNonModalMode(HIGHLIGHTED); } /** Makes this window as embeded with other components (Default). */ public void doEmbedded() { setNonModalMode(EMBEDDED); } /* Set non-modal mode. */ private void setNonModalMode(int mode) { if (_mode != mode) { if (_mode == MODAL) leaveModal(mode); else { _mode = mode; smartUpdate("mode", modeToString(_mode)); } } setVisible(true); } /** Set mode to MODAL and suspend this thread. */ private void enterModal() throws InterruptedException { _mode = MODAL; smartUpdate("mode", modeToString(_mode)); // no need to synchronized (_mutex) because no racing is possible Executions.wait(_mutex); } /** Resumes the suspendded thread and set mode to OVERLAPPED. */ private void leaveModal(int mode) { _mode = mode; smartUpdate("mode", modeToString(_mode)); Executions.notifyAll(_mutex); } private boolean isEventThreadEnabled(boolean attachedRequired) { Desktop desktop = getDesktop(); if (desktop == null) { if (attachedRequired) throw new SuspendNotAllowedException("Not attached, " + this); final Execution exec = Executions.getCurrent(); if (exec == null || (desktop = exec.getDesktop()) == null) return true; // assume enabled (safer) } return desktop.getWebApp().getConfiguration().isEventThreadEnabled(); } /** Makes sure it is not draggable. */ private void checkOverlappable(int mode) { if (!"false".equals(getDraggable())) throw new UiException( "Draggable window cannot be modal, overlapped, popup, or highlighted: " + this); if (mode == MODAL) for (Component comp = this; (comp = comp.getParent()) != null; ) if (!comp.isVisible()) throw new UiException( "One of its ancestors, " + comp + ", is not visible, so unable to be modal or highlighted"); } /** Returns whether to show a close button on the title bar. */ public boolean isClosable() { return _closable; } /** * Sets whether to show a close button on the title bar. If closable, a button is displayed and * the onClose event is sent if an user clicks the button. * * <p>Default: false. * * <p>You can intercept the default behavior by either overriding {@link #onClose}, or listening * the onClose event. * * <p>Note: the close button won't be displayed if no title or caption at all. */ public void setClosable(boolean closable) { if (_closable != closable) { _closable = closable; smartUpdate("closable", closable); // re-init is required } } /** Returns whether the window is sizable. */ public boolean isSizable() { return _sizable; } /** * Sets whether the window is sizable. If true, an user can drag the border to change the window * width. * * <p>Default: false. */ public void setSizable(boolean sizable) { if (_sizable != sizable) { _sizable = sizable; smartUpdate("sizable", sizable); } } /** * Returns whether to show the shadow of an overlapped/popup/modal window. It is meaningless if it * is an embedded window. * * @since 3.6.0 */ public boolean isShadow() { return _shadow; } /** * Sets whether to show the shadow of an overlapped/popup/modal window. It is meaningless if it is * an embedded window. * * <p>Default: true. * * @since 3.6.0 */ public void setShadow(boolean shadow) { if (_shadow != shadow) { _shadow = shadow; smartUpdate("shadow", shadow); } } /** * Returns how to position the window at the client screen. It is meaningless if the embedded mode * is used. * * <p>Default: null which depends on {@link #getMode}: If overlapped or popup, {@link #setLeft} * and {@link #setTop} are assumed. If modal or highlighted, it is centered. */ public String getPosition() { return _pos; } /** * Sets how to position the window at the client screen. It is meaningless if the embedded mode is * used. * * @param pos how to position. It can be null (the default), or a combination of the following * values (by separating with comma). * <dl> * <dt>center * <dd>Position the window at the center. {@link #setTop} and {@link #setLeft} are both * ignored. * <dt>nocenter * <dd>Not to position the window at the center. A modal window, by default, will be * position at the center. By specifying this value could prevent it and the real * position depends on {@link #setTop} and {@link #setLeft} (since 5.0.4) * <dt>left * <dd>Position the window at the left edge. {@link #setLeft} is ignored. * <dt>right * <dd>Position the window at the right edge. {@link #setLeft} is ignored. * <dt>top * <dd>Position the window at the top edge. {@link #setTop} is ignored. * <dt>bottom * <dd>Position the window at the bottom edge. {@link #setTop} is ignored. * <dt>parent * <dd>Position the window relative to its parent. That is, the left and top ({@link * #getTop} and {@link #getLeft}) is an offset to his parent's let-top corner. (since * 3.0.2) * </dl> * <p>For example, "left,center" means to position it at the center of the left edge. */ public void setPosition(String pos) { // Note: we always update since the window might be dragged by an user _pos = pos; smartUpdate("position", pos); } /** * Process the onClose event sent when the close button is pressed. * * <p>Default: detach itself. */ public void onClose() { detach(); } /** Process the onModal event by making itself a modal window. */ public void onModal() throws InterruptedException { doModal(); } /** Returns the CSS style for the content block of the window. */ public String getContentStyle() { return _cntStyle; } /** * Sets the CSS style for the content block of the window. * * <p>Default: null. */ public void setContentStyle(String style) { if (!Objects.equals(_cntStyle, style)) { _cntStyle = style; smartUpdate("contentStyle", _cntStyle); } } /** * Returns the style class used for the content block. * * @see #setContentSclass */ public String getContentSclass() { return _cntSclass; } /** * Sets the style class used for the content block. * * @see #getContentSclass * @since 3.0.0 */ public void setContentSclass(String scls) { if (!Objects.equals(_cntSclass, scls)) { _cntSclass = scls; smartUpdate("contentSclass", scls); } } /** * Makes this window as topmost. It has no effect if this window is embedded. * * @since 5.0.0 */ public void setTopmost() { smartUpdate("topmost", true); } // super protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer) throws java.io.IOException { super.renderProperties(renderer); render(renderer, "title", _title); render(renderer, "maximized", _maximized); render(renderer, "maximizable", _maximizable); render(renderer, "minimized", _minimized); render(renderer, "minimizable", _minimizable); render(renderer, "closable", _closable); render(renderer, "sizable", _sizable); render(renderer, "position", _pos); render(renderer, "contentStyle", _cntStyle); render(renderer, "contentSclass", _cntSclass); if (_minheight != 100) renderer.render("minheight", _minheight); if (_minwidth != 200) renderer.render("minwidth", _minwidth); if (!"none".equals(_border)) renderer.render("border", _border); if (!isShadow()) renderer.render("shadow", false); if (_mode != EMBEDDED) renderer.render("mode", modeToString(_mode)); // render mode as the last property } public String getZclass() { return _zclass == null ? "z-window-" + getMode() : _zclass; } // -- Component --// public void beforeChildAdded(Component child, Component refChild) { if (child instanceof Caption) { if (_caption != null && _caption != child) throw new UiException("Only one caption is allowed: " + this); } else if (refChild instanceof Caption) { throw new UiException("caption must be the first child"); } super.beforeChildAdded(child, refChild); } public boolean insertBefore(Component child, Component refChild) { if (child instanceof Caption) { refChild = getFirstChild(); // always makes caption as the first child if (super.insertBefore(child, refChild)) { _caption = (Caption) child; return true; } return false; } return super.insertBefore(child, refChild); } public void onChildRemoved(Component child) { if (child instanceof Caption) _caption = null; super.onChildRemoved(child); } public void onPageDetached(Page page) { if (_mode == MODAL && getPage() == null) leaveModal(OVERLAPPED); } /** * Changes the visibility of the window. * * <p>Note if you turned on the event thread:<br> * If a modal dialog becomes invisible, the modal state will be ended automatically. In other * words, the mode ({@link #getMode}) will become {@link #OVERLAPPED} and the suspending thread is * resumed. In other words, the modal window ({@link #MODAL}) can not be invisible (while a window * in other modes could be invisible). * * <p>However, if the event thread is not enabled (default), there is no such limitation. In other * words, it remains the same mode when becoming invisible. */ public boolean setVisible(boolean visible) { if (visible == isVisible()) return visible; _maximized = _minimized = false; return setVisible0(visible); } private boolean setVisible0(boolean visible) { if (!visible && _mode == MODAL) { // Hide first to avoid unpleasant effect super.setVisible(false); leaveModal(OVERLAPPED); return true; } return super.setVisible(visible); } // -- super --// public void setDraggable(String draggable) { if (_mode != EMBEDDED) { if (draggable != null && (draggable.length() > 0 && !"false".equals(draggable))) throw new UiException("Only embedded window could be draggable: " + this); } super.setDraggable(draggable); } // Cloneable// public Object clone() { final Window clone = (Window) super.clone(); clone._mutex = new Mutex(); if (clone._caption != null) clone.afterUnmarshal(); return clone; } private void afterUnmarshal() { for (Iterator it = getChildren().iterator(); it.hasNext(); ) { final Object child = it.next(); if (child instanceof Caption) { _caption = (Caption) child; break; } } } // Serializable// private synchronized void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); afterUnmarshal(); } // -- ComponentCtrl --// /** * Processes an AU request. * * <p>Default: in addition to what are handled by {@link XulElement#service}, it also handles * onOpen. * * @since 5.0.0 */ public void service(org.zkoss.zk.au.AuRequest request, boolean everError) { final String cmd = request.getCommand(); if (cmd.equals(Events.ON_OPEN)) { OpenEvent evt = OpenEvent.getOpenEvent(request); setVisible(evt.isOpen()); Events.postEvent(evt); } else if (cmd.equals(Events.ON_MAXIMIZE)) { MaximizeEvent evt = MaximizeEvent.getMaximizeEvent(request); setLeftDirectly(evt.getLeft()); setTopDirectly(evt.getTop()); setWidthDirectly(evt.getWidth()); setHeightDirectly(evt.getHeight()); _maximized = evt.isMaximized(); if (_maximized) setVisibleDirectly(true); Events.postEvent(evt); } else if (cmd.equals(Events.ON_MINIMIZE)) { MinimizeEvent evt = MinimizeEvent.getMinimizeEvent(request); setLeftDirectly(evt.getLeft()); setTopDirectly(evt.getTop()); setWidthDirectly(evt.getWidth()); setHeightDirectly(evt.getHeight()); _minimized = evt.isMinimized(); if (_minimized) { setVisibleDirectly(false); if (_mode == MODAL) leaveModal(OVERLAPPED); } Events.postEvent(evt); } else super.service(request, everError); } /** * Always return false. * * @since 3.6.2 */ public boolean isCollapsible() { return false; } }
/** * Utilities to load (and parse) the Servlet resource. Notice that {@link ResourceCache} and {@link * ResourceLoader} must be used rather than {@link org.zkoss.util.resource.ResourceCache} and {@link * org.zkoss.util.resource.Loader}. * * <p>Usage: * * <ol> * <li>Implements a loader by extending from {@link ResourceLoader}. * <li>Creates a resource cache ({@link ResourceCache}) by use of the loader in the previous step. * <li>Invoke {@link #get} to load the resource. * </ol> * * @author tomyeh */ public class ResourceCaches { private static final Log log = Log.lookup(ResourceCaches.class); /** * Loads, parses and returns the resource of the specified URI, or null if not found. The parser * is defined by the loader defined in {@link ResourceCache}. * * @param cache the resource cache. Note: its loader must extend from {@link ResourceLoader}. * @param path the URI path * @param extra the extra parameter that will be passed to {@link * ResourceLoader#parse(String,File,Object)} and {@link * ResourceLoader#parse(String,URL,Object)} */ public static final <V> V get( ResourceCache<V> cache, ServletContext ctx, String path, Object extra) { // 20050905: Tom Yeh // We don't need to handle the default name if user specifies only a dir // because it is handled by the container directly // And, web developer has to specify <welcome-file> in web.xml URL url = null; if (path == null || path.length() == 0) path = "/"; else if (path.charAt(0) != '/') { if (path.indexOf("://") > 0) { try { url = new URL(path); } catch (java.net.MalformedURLException ex) { throw new SystemException(ex); } } else path = '/' + path; } if (url == null) { if (path.startsWith("/~")) { final ServletContext ctx0 = ctx; final String path0 = path; final int j = path.indexOf('/', 2); final String ctxpath; if (j >= 0) { ctxpath = "/" + path.substring(2, j); path = path.substring(j); } else { ctxpath = "/" + path.substring(2); path = "/"; } final ExtendletContext extctx = Servlets.getExtendletContext(ctx, ctxpath.substring(1)); if (extctx != null) { url = extctx.getResource(path); // if (log.debugable()) log.debug("Resolving "+path0+" to "+url); if (url == null) return null; try { return cache.get(new ResourceInfo(path, url, extra)); } catch (Throwable ex) { final IOException ioex = getIOException(ex); if (ioex == null) throw SystemException.Aide.wrap(ex); log.warningBriefly("Unable to load " + url, ioex); } return null; } ctx = ctx.getContext(ctxpath); if (ctx == null) { // failed // if (log.debugable()) log.debug("Context not found: "+ctxpath); ctx = ctx0; path = path0; // restore } } final String flnm = ctx.getRealPath(path); if (flnm != null) { try { return cache.get(new ResourceInfo(path, new File(flnm), extra)); // it is loader's job to check the existence } catch (Throwable ex) { final IOException ioex = getIOException(ex); if (ioex == null) throw SystemException.Aide.wrap(ex); log.warningBriefly("Unable to load " + flnm, ioex); } return null; } } // try url because some server uses JAR format try { if (url == null) url = ctx.getResource(path); if (url != null) return cache.get(new ResourceInfo(path, url, extra)); } catch (Throwable ex) { final IOException ioex = getIOException(ex); if (ioex == null) throw SystemException.Aide.wrap(ex); log.warningBriefly("Unable to load " + path, ioex); } return null; } // don't eat exceptions other than IOException private static IOException getIOException(Throwable ex) { for (; ex != null; ex = ex.getCause()) if (ex instanceof IOException) return (IOException) ex; return null; } }
/** * @author Georgi Rahnev (http://github.com/rahnev) * @version $Id$ */ public abstract class ServerPushClient extends Thread { private static final Log _log = Log.lookup(ServerPushClient.class); private final Client _client; private boolean _exit = false; public abstract void nextIteration() throws Exception; protected ServerPushClient(Client cleint) { super(); this.setName(this.getClass().getSimpleName() + " for desktop " + cleint.getDesktop().getId()); _client = cleint; _client .getDesktop() .addListener( new DesktopCleanup() { @Override public void cleanup(Desktop desktop) throws Exception { exit(); } }); } public Client getCleint() { return _client; } public void enter() { start(); } public void exit() { _exit = true; } @Override public void run() { while (!_exit) { try { nextIteration(); } catch (Exception ex) { _log.error(ex); exit(); } } } public static ServerPushClient create(Client client) throws EmulationException { Client activeClient = TestUpdateServlet.getActiveClient(); TestUpdateServlet.enter(client); ServerPush serverPush = null; if (!client.getDesktop().isServerPushEnabled()) { client.getDesktop().enableServerPush(true); serverPush = ((DesktopImpl) client.getDesktop()).getServerPush(); client.getDesktop().enableServerPush(false); } else { serverPush = ((DesktopImpl) client.getDesktop()).getServerPush(); } if (serverPush == null) { return new NoServerPushClientEmulator(client); } if (serverPush.getClass().getName().equals("org.zkoss.zkmax.ui.comet.CometServerPush")) { return new CometClientEmulator(client); } if (serverPush instanceof PollingServerPush) { return new PollingClientEmulator(client, 500); } if (activeClient != null) { TestUpdateServlet.leave(activeClient); } throw new AssertionError("Unsupported server push model " + serverPush.getClass().getName()); } }
/** * Utilities to access {@link Component}. * * @author tomyeh */ public class Components { private static final Log log = Log.lookup(Components.class); private static final Log _zklog = Log.lookup("org.zkoss.zk.log"); protected Components() {} /** * Returns the parent of the ID space, or null if not found. * * @since 5.0.0 */ public static IdSpace getParentIdSpace(IdSpace idspace) { if (idspace instanceof Component) { final Component c = (Component) idspace; final Component p = c.getParent(); return p != null ? p.getSpaceOwner() : c.getPage(); } return null; } /** * Sorts the components in the list. * * <p>Note: you cannot use {@link Collections#sort} to sort {@link Component#getChildren} because * Collections.sort might cause some replicated item in the list. * * @see #sort(List, int, int, Comparator) */ public static void sort(List<? extends Component> list, Comparator<? super Component> cpr) { sort(list, 0, list.size(), cpr); } /** * Replaces a component with another. * * @param oldc the component to remove. * @param newc the component to add * @exception IllegalArgumentException if oldc's parent and page are both null. * @since 3.5.2 */ public static void replace(Component oldc, Component newc) { final Component p = oldc.getParent(), sib = oldc.getNextSibling(); if (p != null) { oldc.detach(); p.insertBefore(newc, sib); } else { final Page page = oldc.getPage(); if (page == null) throw new IllegalArgumentException("Neither child nor attached, " + oldc); oldc.detach(); if (newc.getParent() != null) newc.detach(); newc.setPageBefore(page, sib); } } /** * Replaces all children of the specified component. It is the same as * * <pre><code>parent.getChildren().clear(); * parent.getChildren().addAll(newChildren); * </code></pre> * * @since 3.5.2 */ public static void replaceChildren(Component parent, Collection<Component> newChildren) { final Collection<Component> children = parent.getChildren(); children.clear(); children.addAll(newChildren); } /** * Sorts the components in the list. * * @param list the list to be sorted * @param from the index of the first element (inclusive) to be sorted * @param to the index of the last element (exclusive) to be sorted * @param cpr the comparator to determine the order of the list. * @since 3.5.0 */ public static void sort( List<? extends Component> list, int from, int to, Comparator<? super Component> cpr) { final Component ary[] = CollectionsX.toArray(list, new Component[0], from, to); Arrays.sort(ary, cpr); ListIterator<? extends Component> it = list.listIterator(from); int j = 0, k = to - from; for (; it.hasNext() && --k >= 0; ++j) { if (it.next() != ary[j]) { it.remove(); if (it.hasNext() && --k >= 0) { if (it.next() == ary[j]) continue; it.previous(); ++k; } break; } } while (it.hasNext() && --k >= 0) { it.next(); it.remove(); } for (; j < ary.length; ++j) add(list, from + j, ary[j]); } @SuppressWarnings("unchecked") private static void add(List list, int index, Object o) { // to minimize the unchecked range list.add(index, o); } /** * Returns the root component of the specified one. Notice that it could return <code>comp</code>, * if it is already a root component (or it is null). * * @since 3.6.3 */ public static Component getRoot(Component comp) { if (comp == null) return null; for (; ; ) { final Component p = comp.getParent(); if (p == null) return comp; comp = p; } } /** * Tests whether node1 is an ancessor of node 2. If node1 and node2 is the same, true is returned. */ public static boolean isAncestor(Component node1, Component node2) { for (; node2 != null; node2 = node2.getParent()) { if (node1 == node2) return true; } return false; } /** * Removes all children of the specified component. It is the same as <code> * comp.getChildren().clear()</code>. */ public static void removeAllChildren(Component comp) { comp.getChildren().clear(); } /** * Returns the component definition of the specified class in all language of the specified * device, or null if not found * * @param deviceType the device type ({@link org.zkoss.zk.device.Device}), such as ajax. It cannot * be null. * @param cls the implementation class of the component. * @since 5.0.0 */ public static final ComponentDefinition getDefinitionByDeviceType(String deviceType, Class cls) { for (LanguageDefinition ld : LanguageDefinition.getByDeviceType(deviceType)) { try { return ld.getComponentDefinition(cls); } catch (DefinitionNotFoundException ex) { // ignore } } return null; } /** * Returns whether this component is real visible (all its parents are visible). * * <p>Note: true is returned if comp is null. In other words, it can be used to examine parent's * real visibity even if it is a root component, such as <code> * Components.isRealVisible(getParent())</code>. * * @see Component#isVisible */ public static boolean isRealVisible(Component comp) { for (; comp != null; comp = comp.getParent()) if (!comp.isVisible()) return false; return true; } /** * Returns a collection of visible children. * * <p>The performance of the returned collection's size() is NO GOOD. */ public static Collection<Component> getVisibleChildren(Component comp) { final Collection<Component> children = comp.getChildren(); return new AbstractCollection<Component>() { public int size() { int size = 0; for (Component c : children) { if (c.isVisible()) ++size; } return size; } public Iterator<Component> iterator() { return new Iterator<Component>() { final Iterator<Component> _it = children.iterator(); Component _next; public boolean hasNext() { if (_next != null) return true; _next = getNextVisible(false); return _next != null; } public Component next() { if (_next != null) { final Component c = _next; _next = null; return c; } return getNextVisible(true); } public void remove() { throw new UnsupportedOperationException(); } private Component getNextVisible(boolean blind) { while (blind || _it.hasNext()) { final Component c = (Component) _it.next(); if (c.isVisible()) return c; } return null; } }; } }; } /** * Converts a string to an integer that can be used to access {@link * Component#getAttribute(String, int)} */ public static final int getScope(String scope) { if ("component".equals(scope)) return Component.COMPONENT_SCOPE; if ("space".equals(scope)) return Component.SPACE_SCOPE; if ("page".equals(scope)) return Component.PAGE_SCOPE; if ("desktop".equals(scope)) return Component.DESKTOP_SCOPE; if ("session".equals(scope)) return Component.SESSION_SCOPE; if ("application".equals(scope)) return Component.APPLICATION_SCOPE; if ("request".equals(scope)) return Component.REQUEST_SCOPE; throw new IllegalArgumentException("Unknown scope: " + scope); } /** * Converts an integer to the string representing the scope. * * @param scope one of {@link Component#COMPONENT_SCOPE}, {@link Component#SPACE_SCOPE}, {@link * Component#PAGE_SCOPE}, {@link Component#DESKTOP_SCOPE}, {@link Component#SESSION_SCOPE}, * {@link Component#REQUEST_SCOPE}, and {@link Component#APPLICATION_SCOPE}. */ public static final String scopeToString(int scope) { switch (scope) { case Component.COMPONENT_SCOPE: return "component"; case Component.SPACE_SCOPE: return "space"; case Component.PAGE_SCOPE: return "page"; case Component.DESKTOP_SCOPE: return "desktop"; case Component.SESSION_SCOPE: return "session"; case Component.APPLICATION_SCOPE: return "application"; case Component.REQUEST_SCOPE: return "request"; } throw new IllegalArgumentException("Unknown scope: " + scope); } /** * Converts a component to a path (relavant to another component). It is usefully to implement a * serializable component that contains a reference to another component. In this case, we can not * serializes the reference directly (otherwise, another component will be created, when * deserialized). * * <p>Rather, it is better to store the path related, and then restore it back to a component by * calling {@link #pathToComponent}. * * @param comp the component to be converted to path. It cannot be null. * @param ref the component used to generated the path from. It cannot be null. * @return the path. Notice that you have to use {@link #pathToComponent} to convert it back. * @exception UnsupportedOperationException if we cannot find a path to the component to write. * @since 3.0.0 */ public static final String componentToPath(Component comp, Component ref) { // Implementation Note: // The path being written is a bit different to Path, if ref // is not an space owner // For example, if comp is the space owner, "" is written. // If comp is the same as ref, "." is written. if (comp == null) { return null; } else if (comp == ref) { return "."; } else { final String id = comp.getId(); if (!(comp instanceof IdSpace) && id.length() == 0) throw new UnsupportedOperationException( "comp must be assigned with ID or a space owner: " + comp); final StringBuffer sb = new StringBuffer(128); for (IdSpace space = ref.getSpaceOwner(); ; ) { if (comp == space) { return sb.toString(); // could be "" // we don't generate id to make it work even if // its ID is changed } else if (space.getFellowIfAny(id) == comp) { if (sb.length() > 0) sb.append('/'); return sb.append(id).toString(); } if (sb.length() > 0) sb.append('/'); sb.append(".."); final Component parent = space instanceof Component ? ((Component) space).getParent() : null; if (parent == null) throw new UnsupportedOperationException("Unable to locate " + comp + " from " + ref); space = parent.getSpaceOwner(); } } } /** * Converts a path, generated by {@link #componentToPath}, to a component. * * @param ref the component used to generated the path from. It cannot be null. It is the same as * the one when calling {@link #componentToPath}. * @since 3.0.0 */ public static final Component pathToComponent(String path, Component ref) { if (path == null) { return null; } else if (".".equals(path)) { return ref; } else if ("".equals(path)) { final IdSpace owner = ref.getSpaceOwner(); if (!(owner instanceof Component)) throw new IllegalStateException("The component is moved after serialized: " + ref); return (Component) owner; } return Path.getComponent(ref.getSpaceOwner(), path); } /** @deprecated As of release 6.0.0, replaced with {@link ConventionWires}. */ public static final void wireFellows(IdSpace idspace, Object controller) { ConventionWires.wireFellows(idspace, controller); } /** @deprecated As of release 6.0.0, replaced with {@link ConventionWires}. */ public static final void wireFellows(IdSpace idspace, Object controller, char separator) { ConventionWires.wireFellows(idspace, controller, separator); } /** @deprecated As of release 6.0.0, replaced with {@link ConventionWires}. */ public static final void wireFellows( IdSpace idspace, Object controller, char separator, boolean ignoreZScript, boolean ignoreXel) { ConventionWires.wireFellows(idspace, controller, separator, ignoreZScript, ignoreXel); } /** @deprecated As of release 6.0.0, replaced with {@link ConventionWires}. */ public static final void wireVariables(Component comp, Object controller) { ConventionWires.wireVariables(comp, controller); } /** @deprecated As of release 6.0.0, replaced with {@link ConventionWires}. */ public static final void wireVariables(Component comp, Object controller, char separator) { ConventionWires.wireVariables(comp, controller, separator); } /** @deprecated As of release 6.0.0, replaced with {@link ConventionWires}. */ public static final void wireVariables( Component comp, Object controller, char separator, boolean ignoreZScript, boolean ignoreXel) { ConventionWires.wireVariables(comp, controller, separator, ignoreZScript, ignoreXel); } /** @deprecated As of release 6.0.0, replaced with {@link ConventionWires}. */ public static final void wireVariables(Page page, Object controller) { ConventionWires.wireVariables(page, controller); } /** @deprecated As of release 6.0.0, replaced with {@link ConventionWires}. */ public static final void wireVariables(Page page, Object controller, char separator) { ConventionWires.wireVariables(page, controller, separator); } /** @deprecated As of release 6.0.0, replaced with {@link ConventionWires}. */ public static final void wireVariables( Page page, Object controller, char separator, boolean ignoreZScript, boolean ignoreXel) { ConventionWires.wireVariables(page, controller, separator, ignoreZScript, ignoreXel); } /** @deprecated As of release 6.0.0, replaced with {@link ConventionWires}. */ public static final void wireController(Component comp, Object controller) { ConventionWires.wireController(comp, controller); } /** @deprecated As of release 6.0.0, replaced with {@link ConventionWires}. */ public static final void wireController(Component comp, Object controller, char separator) { ConventionWires.wireController(comp, controller, separator); } /** @deprecated As of release 6.0.0, replaced with {@link ConventionWires}. */ public static final void wireController( Component comp, Object controller, char separator, boolean ignoreZScript, boolean ignoreXel) { ConventionWires.wireController(comp, controller, separator); } /** @deprecated As of release 6.0.0, replaced with {@link ConventionWires}. */ public static final void wireImplicit(Component comp, Object controller) { ConventionWires.wireImplicit(comp, controller); } /** @deprecated As of release 6.0.0, replaced with {@link ConventionWires}. */ public static void addForwards(Component comp, Object controller) { ConventionWires.addForwards(comp, controller); } /** @deprecated As of release 6.0.0, replaced with {@link ConventionWires}. */ public static void addForwards(Component comp, Object controller, char separator) { ConventionWires.addForwards(comp, controller, separator); } /** * Returns whether the given id is an implicit ZK object id. * * @param id Component id * @return whether the given name is a implicit object. * @since 3.5.2 */ public static boolean isImplicit(String id) { return IMPLICIT_NAMES.contains(id); } /** * Retuns a readonly collection of the names of the implicit objects. * * @since 6.0.0 */ public static Collection<String> getImplicitNames() { return IMPLICIT_NAMES; } private static final Set<String> IMPLICIT_NAMES = new HashSet<String>(); static { final String[] names = { "application", "applicationScope", "arg", "componentScope", "desktop", "desktopScope", "execution", "event", // since 3.6.1, #bug 2681819: normal page throws exception after installed zkspring "self", "session", "sessionScope", "spaceOwner", "spaceScope", "page", "pageScope", "requestScope", "param" }; for (int j = 0; j < names.length; ++j) IMPLICIT_NAMES.add(names[j]); } /** * Retuns the implicit object of the specified name, or null if not found. * * <p>Notice that it does check for the current scope ({@link * org.zkoss.zk.ui.ext.Scopes#getCurrent}). Rather, {@link org.zkoss.zk.ui.ext.Scopes#getImplicit} * depends on this method. * * @param page the page. If page is null and comp is not, comp.getPage() is assumed * @see org.zkoss.zk.ui.ext.Scopes#getImplicit * @since 5.0.0 */ public static Object getImplicit(Page page, Component comp, String name) { if (comp != null && page == null) page = getCurrentPage(comp); if ("log".equals(name)) return _zklog; if ("self".equals(name)) return comp != null ? comp : (Object) page; if ("spaceOwner".equals(name)) return comp != null ? comp.getSpaceOwner() : (Object) page; if ("page".equals(name)) return page; if ("desktop".equals(name)) return comp != null ? getDesktop(comp) : page.getDesktop(); if ("session".equals(name)) return comp != null ? getSession(comp) : page.getDesktop().getSession(); if ("application".equals(name)) return comp != null ? getWebApp(comp) : page.getDesktop().getWebApp(); if ("componentScope".equals(name)) return comp != null ? comp.getAttributes() : Collections.EMPTY_MAP; if ("spaceScope".equals(name)) { final Scope scope = comp != null ? (Scope) comp.getSpaceOwner() : (Scope) page; return scope != null ? scope.getAttributes() : Collections.EMPTY_MAP; } if ("pageScope".equals(name)) return page != null ? page.getAttributes() : Collections.EMPTY_MAP; if ("desktopScope".equals(name)) { final Desktop dt = comp != null ? getDesktop(comp) : page.getDesktop(); return dt != null ? dt.getAttributes() : Collections.EMPTY_MAP; } if ("sessionScope".equals(name)) { final Session sess = comp != null ? getSession(comp) : page.getDesktop().getSession(); return sess != null ? sess.getAttributes() : Collections.EMPTY_MAP; } if ("applicationScope".equals(name)) { final WebApp app = comp != null ? getWebApp(comp) : page.getDesktop().getWebApp(); return app != null ? app.getAttributes() : Collections.EMPTY_MAP; } if ("requestScope".equals(name)) return REQUEST_SCOPE_PROXY; if ("execution".equals(name)) return EXECUTION_PROXY; if ("arg".equals(name)) { final Execution exec = Executions.getCurrent(); return exec != null ? exec.getArg() : null; // bug 2937096: composer.arg shall be statically wired // arg is a Map prepared by application developer, so can be wired statically } if ("param".equals(name)) { final Execution exec = Executions.getCurrent(); return exec != null ? exec.getParameterMap() : null; // bug 2945974: composer.param shall be statically wired // Note that request parameter is prepared by servlet container, you shall not // copy the reference to this map; rather, you shall clone the key-value pair one-by-one. } // 20090314, Henri Chen: No way to suppport "event" with an event proxy because // org.zkoss.zk.Event is not an interface return null; } /** * Retuns the implicit object of the specified name, or null if not found. * * <p>It is the same as getImplicit(null, comp, name). * * @since 3.6.0 */ public static Object getImplicit(Component comp, String name) { return getImplicit(null, comp, name); } /** * Retuns the implicit object of the specified name, or null if not found. * * <p>It is the same as getImplicit(page, null, name). * * @since 3.6.0 */ public static Object getImplicit(Page page, String name) { return getImplicit(page, null, name); } private static Desktop getDesktop(Component comp) { final Desktop dt = comp.getDesktop(); if (dt != null) return dt; final Execution exec = Executions.getCurrent(); return exec != null ? exec.getDesktop() : null; } private static WebApp getWebApp(Component comp) { final Desktop dt = getDesktop(comp); return dt != null ? dt.getWebApp() : null; } private static Session getSession(Component comp) { final Desktop dt = getDesktop(comp); return dt != null ? dt.getSession() : null; } /** * Returns the page of the give component, or the current page if the component is null or it * doesn't belong to any page. The current page is retrieved by {@link * ExecutionCtrl#getCurrentPage} or the current execution. This method returns null if no * execution or no current page at all. * * @param comp the component to retrieve the page. Ignored if null. * @since 6.0.0 */ public static Page getCurrentPage(Component comp) { if (comp != null) { Page page = comp.getPage(); if (page != null) return page; } final Execution exec = Executions.getCurrent(); return exec != null ? ((ExecutionCtrl) exec).getCurrentPage() : null; } /** Execution Proxy */ public static final Exec EXECUTION_PROXY = new Exec(); /** Request Scope Proxy */ public static final RequestScope REQUEST_SCOPE_PROXY = new RequestScope(); // Proxy to read current execution private static class Exec implements Execution { private static final Execution exec() { return Executions.getCurrent(); } public void addAuResponse(AuResponse response) { exec().addAuResponse(response); } public void addAuResponse(String key, AuResponse response) { exec().addAuResponse(key, response); } public Component createComponents(PageDefinition pagedef, Component parent, Map<?, ?> arg) { return exec().createComponents(pagedef, parent, arg); } public Component createComponents(String uri, Component parent, Map<?, ?> arg) { return exec().createComponents(uri, parent, arg); } public Component createComponentsDirectly( String content, String extension, Component parent, Map<?, ?> arg) { return exec().createComponentsDirectly(content, extension, parent, arg); } public Component createComponentsDirectly( Document content, String extension, Component parent, Map<?, ?> arg) { return exec().createComponentsDirectly(content, extension, parent, arg); } public Component createComponentsDirectly( Reader reader, String extension, Component parent, Map<?, ?> arg) throws IOException { return exec().createComponentsDirectly(reader, extension, parent, arg); } public Component createComponents( PageDefinition pagedef, Component parent, Component insertBefore, VariableResolver resolver) { return exec().createComponents(pagedef, parent, insertBefore, resolver); } public Component createComponents( String uri, Component parent, Component insertBefore, VariableResolver resolver) { return exec().createComponents(uri, parent, insertBefore, resolver); } public Component createComponentsDirectly( String content, String extension, Component parent, Component insertBefore, VariableResolver resolver) { return exec().createComponentsDirectly(content, extension, parent, insertBefore, resolver); } public Component createComponentsDirectly( Document content, String extension, Component parent, Component insertBefore, VariableResolver resolver) { return exec().createComponentsDirectly(content, extension, parent, insertBefore, resolver); } public Component createComponentsDirectly( Reader reader, String extension, Component parent, Component insertBefore, VariableResolver resolver) throws IOException { return exec().createComponentsDirectly(reader, extension, parent, insertBefore, resolver); } public Component[] createComponents(PageDefinition pagedef, Map<?, ?> arg) { return exec().createComponents(pagedef, arg); } public Component[] createComponents(String uri, Map<?, ?> arg) { return exec().createComponents(uri, arg); } public Component[] createComponentsDirectly(String content, String extension, Map<?, ?> arg) { return exec().createComponentsDirectly(content, extension, arg); } public Component[] createComponentsDirectly(Document content, String extension, Map<?, ?> arg) { return exec().createComponentsDirectly(content, extension, arg); } public Component[] createComponentsDirectly(Reader reader, String extension, Map<?, ?> arg) throws IOException { return exec().createComponentsDirectly(reader, extension, arg); } public String encodeURL(String uri) { return exec().encodeURL(uri); } public Object evaluate(Component comp, String expr, Class expectedType) { return exec().evaluate(comp, expr, expectedType); } public Object evaluate(Page page, String expr, Class expectedType) { return exec().evaluate(page, expr, expectedType); } public void forward(Writer writer, String page, Map<String, ?> params, int mode) throws IOException { exec().forward(writer, page, params, mode); } public void forward(String page) throws IOException { exec().forward(page); } public Map<?, ?> getArg() { return exec().getArg(); } public Object getAttribute(String name) { return exec().getAttribute(name); } public boolean hasAttribute(String name) { return exec().hasAttribute(name); } public Object getAttribute(String name, boolean recurse) { return exec().getAttribute(name, recurse); } public boolean hasAttribute(String name, boolean recurse) { return exec().hasAttribute(name, recurse); } public Object setAttribute(String name, Object value, boolean recurse) { return exec().setAttribute(name, value, recurse); } public Object removeAttribute(String name, boolean recurse) { return exec().removeAttribute(name, recurse); } public boolean addScopeListener(ScopeListener listener) { return exec().addScopeListener(listener); } public boolean removeScopeListener(ScopeListener listener) { return exec().removeScopeListener(listener); } public Map<String, Object> getAttributes() { return exec().getAttributes(); } public String getContextPath() { return exec().getContextPath(); } public Desktop getDesktop() { return exec().getDesktop(); } public Evaluator getEvaluator(Page page, Class<? extends ExpressionFactory> expfcls) { return exec().getEvaluator(page, expfcls); } public Evaluator getEvaluator(Component comp, Class<? extends ExpressionFactory> expfcls) { return exec().getEvaluator(comp, expfcls); } public String getLocalAddr() { return exec().getLocalAddr(); } public String getLocalName() { return exec().getLocalName(); } public int getLocalPort() { return exec().getLocalPort(); } public Object getNativeRequest() { return exec().getNativeRequest(); } public Object getNativeResponse() { return exec().getNativeResponse(); } public PageDefinition getPageDefinition(String uri) { return exec().getPageDefinition(uri); } public PageDefinition getPageDefinitionDirectly(String content, String extension) { return exec().getPageDefinitionDirectly(content, extension); } public PageDefinition getPageDefinitionDirectly(Document content, String extension) { return exec().getPageDefinitionDirectly(content, extension); } public PageDefinition getPageDefinitionDirectly(Reader reader, String extension) throws IOException { return exec().getPageDefinitionDirectly(reader, extension); } public String getParameter(String name) { return exec().getParameter(name); } public Map<String, String[]> getParameterMap() { return exec().getParameterMap(); } public String[] getParameterValues(String name) { return exec().getParameterValues(name); } public String getRemoteAddr() { return exec().getRemoteAddr(); } public String getRemoteHost() { return exec().getRemoteHost(); } public String getRemoteUser() { return exec().getRemoteUser(); } public String getServerName() { return exec().getServerName(); } public int getServerPort() { return exec().getServerPort(); } public String getScheme() { return exec().getScheme(); } public String getUserAgent() { return exec().getUserAgent(); } public Principal getUserPrincipal() { return exec().getUserPrincipal(); } public VariableResolver getVariableResolver() { return exec().getVariableResolver(); } public void include(Writer writer, String page, Map<String, ?> params, int mode) throws IOException { exec().include(writer, page, params, mode); } public void include(String page) throws IOException { exec().include(page); } public boolean isAsyncUpdate(Page page) { return exec().isAsyncUpdate(page); } @Override public Double getBrowser(String name) { return exec().getBrowser(name); } @Override public String getBrowser() { return exec().getBrowser(); } /** @deprecated As of release 6.0.0, replaced with {@link #getBrowser(String)}. */ public boolean isBrowser() { return exec().isBrowser(); } /** @deprecated As of release 6.0.0, replaced with {@link #getBrowser(String)}. */ public boolean isBrowser(String type) { return exec().isBrowser(type); } /** @deprecated As of release 6.0.0, replaced with {@link #getBrowser(String)}. */ public boolean isExplorer() { return exec().isExplorer(); } /** @deprecated As of release 6.0.0, replaced with {@link #getBrowser(String)}. */ public boolean isExplorer7() { return exec().isExplorer7(); } /** @deprecated As of release 6.0.0, replaced with {@link #getBrowser(String)}. */ public boolean isOpera() { return exec().isOpera(); } /** @deprecated As of release 6.0.0, replaced with {@link #getBrowser(String)}. */ public boolean isGecko() { return exec().isGecko(); } /** @deprecated As of release 6.0.0, replaced with {@link #getBrowser(String)}. */ public boolean isGecko3() { return exec().isGecko3(); } /** @deprecated As of release 6.0.0, replaced with {@link #getBrowser(String)}. */ public boolean isHilDevice() { return exec().isHilDevice(); } /** @deprecated As of release 6.0.0, replaced with {@link #getBrowser(String)}. */ public boolean isSafari() { return exec().isSafari(); } /** @deprecated As of release 6.0.0, replaced with {@link #getBrowser(String)}. */ public boolean isRobot() { return exec().isRobot(); } public boolean isForwarded() { return exec().isForwarded(); } public boolean isIncluded() { return exec().isIncluded(); } public boolean isUserInRole(String role) { return exec().isUserInRole(role); } public boolean isVoided() { return exec().isVoided(); } public void popArg() { exec().popArg(); } public void postEvent(Event evt) { exec().postEvent(evt); } public void postEvent(int priority, Event evt) { exec().postEvent(priority, evt); } public void postEvent(int priority, Component realTarget, Event evt) { exec().postEvent(priority, realTarget, evt); } public void pushArg(Map<?, ?> arg) { exec().pushArg(arg); } public Object removeAttribute(String name) { return exec().removeAttribute(name); } public void sendRedirect(String uri) { exec().sendRedirect(uri); } public void sendRedirect(String uri, String target) { exec().sendRedirect(uri, target); } public Object setAttribute(String name, Object value) { return exec().setAttribute(name, value); } public void setVoided(boolean voided) { exec().setVoided(voided); } public String toAbsoluteURI(String uri, boolean skipInclude) { return exec().toAbsoluteURI(uri, skipInclude); } public void addResponseHeader(String name, String value) { exec().addResponseHeader(name, value); } public void addResponseHeader(String name, Date value) { exec().addResponseHeader(name, value); } public boolean containsResponseHeader(String name) { return exec().containsResponseHeader(name); } public String getHeader(String name) { return exec().getHeader(name); } public Iterable<String> getHeaderNames() { return exec().getHeaderNames(); } public Iterable<String> getHeaders(String name) { return exec().getHeaders(name); } public void setResponseHeader(String name, String value) { exec().setResponseHeader(name, value); } public void setResponseHeader(String name, Date value) { exec().setResponseHeader(name, value); } public Session getSession() { return exec().getSession(); } public String locate(String path) { return exec().locate(path); } public boolean addVariableResolver(VariableResolver resolver) { return exec().addVariableResolver(resolver); } public boolean removeVariableResolver(VariableResolver resolver) { return exec().removeVariableResolver(resolver); } public boolean hasVariableResolver(VariableResolver resolver) { return exec().hasVariableResolver(resolver); } public String toString() { return Objects.toString(exec()); } public int hashCode() { return Objects.hashCode(exec()); } public boolean equals(Object o) { if (o instanceof Exec) return Objects.equals(exec(), ((Exec) o).exec()); return Objects.equals(exec(), o); } @Override public void log(String msg) { exec().log(msg); } @Override public void log(String msg, Throwable ex) { exec().log(msg, ex); } } // Proxy to read current requestScope private static class RequestScope implements Map<String, Object> { protected Map<String, Object> req() { return Executions.getCurrent().getAttributes(); } public void clear() { req().clear(); } public boolean containsKey(Object key) { return req().containsKey(key); } public boolean containsValue(Object value) { return req().containsValue(value); } public Set<Map.Entry<String, Object>> entrySet() { return req().entrySet(); } public Object get(Object key) { return req().get(key); } public boolean isEmpty() { return req().isEmpty(); } public Set<String> keySet() { return req().keySet(); } public Object put(String key, Object value) { return req().put(key, value); } public void putAll(Map<? extends String, ? extends Object> arg0) { req().putAll(arg0); } public Object remove(Object key) { return req().remove(key); } public int size() { return req().size(); } public Collection<Object> values() { return req().values(); } public String toString() { return Objects.toString(req()); } public int hashCode() { return Objects.hashCode(req()); } public boolean equals(Object o) { if (o instanceof RequestScope) return Objects.equals(req(), ((RequestScope) o).req()); return Objects.equals(req(), o); } } }
/** * Track Binding CRUD and dependent tracking management. * * @author henrichen * @since 6.0.0 */ public class BindUiLifeCycle implements UiLifeCycle { static final Log log = Log.lookup(BindUiLifeCycle.class); private static Extension _ext; public void afterComponentAttached(Component comp, Page page) { if (comp.getDesktop() != null) { // check if this component already binded Binder selfBinder = BinderUtil.getBinder(comp); if (selfBinder == null) { // check if parent exists any binder Binder parentBinder = BinderUtil.getBinder(comp, true); // post event to let the binder to handle binding later if (parentBinder != null && (parentBinder instanceof BinderImpl)) { // ZK-603, ZK-604, ZK-605 // register internal ON_BIND_INIT event listener to delay the timing of init and loading // bindings comp.addEventListener( 10000, BinderImpl.ON_BIND_INIT, new EventListener<Event>() { public void onEvent(Event event) throws Exception { final Component comp = event.getTarget(); comp.removeEventListener(BinderImpl.ON_BIND_INIT, this); // ZK-611 have wrong binding on a removed treecell in a template // if it was detached, ignore it if (comp.getPage() == null) { return; } final Binder innerBinder = BinderUtil.getBinder(comp); if (innerBinder != null) { // it was already handled by innerBinder, ignore it return; } // ZK-1640 command send 2 wrong ViewModel // check if there any parent binder again, don't use out-side parentBinder, it is // not correct Binder binder = BinderUtil.getBinder(comp, true); if (binder == null) { return; } // ZK-1699 Performance issue ZK-Bind getters are called multiple times // check if it is handling, if yes then skip to evaluate it. if (getExtension().isLifeCycleHandling(comp)) { return; } if (binder instanceof AnnotateBinder) { new AnnotateBinderHelper(binder).initComponentBindings(comp); } // ZK-1699, mark the comp and it's children are handling. // note:mark handing before load, because of some load will change(create or // reset) the children structure // (consider F00769.zul if you bind to tree open , it will load children in // loadComponent) getExtension().markLifeCycleHandling(comp); binder.loadComponent(comp, true); // [Dennis,20120925], this code was added when fixing issue zk-739, // but , inside binder.initComponentBindings, it shall do this already, I am not // sure why. if (comp.getAttribute(BinderImpl.VAR) != null) BinderUtil.markHandling(comp, binder); } }); // post ON_BIND_INIT event Events.postEvent(new Event(BinderImpl.ON_BIND_INIT, comp)); } } } } public void afterComponentDetached(Component comp, Page prevpage) { removeBindings(comp); } public void afterComponentMoved(Component parent, Component child, Component prevparent) { // do nothing } public void afterPageAttached(Page page, Desktop desktop) { // do nothing } public void afterPageDetached(Page page, Desktop prevdesktop) { final Collection<Component> comps = page.getRoots(); for (final Iterator<Component> it = comps.iterator(); it.hasNext(); ) { final Component comp = it.next(); removeBindings(comp); } } private void removeBindings(Component comp) { removeBindings0(comp); for (final Iterator<Component> it = comp.getChildren().iterator(); it.hasNext(); ) { final Component kid = it.next(); if (kid != null) { removeBindings(kid); // recursive } } } private void removeBindings0(Component comp) { // A component with renderer; might need to remove $MODEL$ final Object installed = comp.removeAttribute(BinderImpl.RENDERER_INSTALLED); if (installed != null) { BindELContext.removeModel(comp); } final Binder binder = BinderUtil.getBinder(comp); if (binder != null) { binder.removeBindings(comp); } getExtension().removeLifeCycleHandling(comp); } private static Extension getExtension() { if (_ext == null) { synchronized (BindUiLifeCycle.class) { if (_ext == null) { String clsnm = Library.getProperty("org.zkoss.bind.tracker.impl.extension"); if (clsnm != null) { try { _ext = (Extension) Classes.newInstanceByThread(clsnm); } catch (Throwable ex) { log.realCauseBriefly("Unable to instantiate " + clsnm, ex); } } if (_ext == null) _ext = new DefaultExtension(); } } } return _ext; } /** * Internal use only. Mark a component and it's children are handling already in current * execution. So, if the component attach to component tree(cause {@code * #afterComponentAttached(Component, Page)}, BindUiLifeCycle will not process it again. */ public static void markLifeCycleHandling(Component comp) { getExtension().markLifeCycleHandling(comp); } /** * An interface used to extend the {@code BindUiLifeCycle}. The class name of the extension shall * be specified in the library properties called org.zkoss.bind.tracker.impl.extension. * * <p>Notice that it is used only internally. * * @since 6.5.3 */ public static interface Extension { public void markLifeCycleHandling(Component comp); public boolean isLifeCycleHandling(Component comp); public void removeLifeCycleHandling(Component comp); } private static class DefaultExtension implements Extension { public void markLifeCycleHandling(Component comp) {} public boolean isLifeCycleHandling(Component comp) { return false; } public void removeLifeCycleHandling(Component comp) {} } }
/** * Utilities related to the Object class. * * @author tomyeh */ public class Objects { private static final Log log = Log.lookup(Objects.class); /** Denotes unknown. It is useful if both null and unknown is required. */ public static final Object UNKNOWN = new Object() { // anonymous class public final String toString() { return "(null)"; // a little different from null } }; /** The zero long. */ public static final Long ZERO_LONG = new Long(0L); /** The zero integer. */ public static final Integer ZERO_INTEGER = new Integer(0); /** The zero short. */ public static final Short ZERO_SHORT = new Short((short) 0); /** The zero integer. */ public static final Byte ZERO_BYTE = new Byte((byte) 0); /** The zero float. */ public static final Float ZERO_FLOAT = new Float(0F); /** The zero double. */ public static final Double ZERO_DOUBLE = new Double(0D); /** * Represents 0 in big decimal. The same as {@link org.zkoss.math.BigDecimals#ZERO}. * * @see org.zkoss.math.BigDecimals#ONE */ public static final BigDecimal ZERO_BIG_DECIMAL = new BigDecimal(0D); /** Represents 0 in big integer. Same as {@link org.zkoss.math.BigIntegers#ZERO}. */ public static final BigInteger ZERO_BIG_INTEGER = BigInteger.ZERO; /** The null character. */ public static final Character NULL_CHARACTER = new Character('\u0000'); /** A separator char that is from the Unicode reserved area. */ public static final char SEPARATOR_CHAR = 0xf77a; // DON'T CHANGE IT (Or, db broke) /** The second separator char that is from the Unicode reserved area. */ public static final char SEPARATOR_CHAR2 = (char) (SEPARATOR_CHAR - 1); // DON'T CHANGE IT (Or, db broke) /** A separator char that is from the Unicode reserved area. */ public static final String SEPARATOR_STRING = "" + SEPARATOR_CHAR; /** The path seperator character that is used to construct a path. See org.zkoss.i3.ds.Path. */ public static final char PATH_SEPARATOR_CHAR = '/'; /** The path seperator string that is used to construct a path. See org.zkoss.i3.ds.Path. */ public static final String PATH_SEPARATOR_STRING = "" + PATH_SEPARATOR_CHAR; /** The horizontal bar 0 (i.e., .........). */ public static final String BAR0_STRING = "..\t\t.............................."; /** The horizontal bar 1 (i.e., ---------). */ public static final String BAR1_STRING = "--\t\t------------------------------"; /** The horizontal bar 2 (i.e., =========). */ public static final String BAR2_STRING = "==\t\t=============================="; /** * Returns the next hash value by giving the previous one and a new one. The caller usually uses a * loop to accumulate all related fields. * * @param prevHashVal the previous hash value returned by this method; 0 if it is the first call. * @param newVal the new value to put in * @return the new hash value */ public static final int nextHashCode(int prevHashVal, int newVal) { return prevHashVal * 31 + newVal; } /** * Generates hash codes for an array of boolean. It is suggested to cache the hash code. * * @param v the array * @return the hash code */ public static final int hashCode(boolean[] v) { int h = 1; // not to return 0 if possible, so caller cache it easily for (int j = v.length; --j >= 0; ) h = nextHashCode(h, v[j] ? 1 : 0); return h; } /** * Generates hash codes for an array of bytes. It is suggested to cache the hash code. * * @param v the array * @return the hash code */ public static final int hashCode(byte[] v) { int h = 1; // not to return 0 if possible, so caller cache it easily for (int j = v.length; --j >= 0; ) h = nextHashCode(h, v[j]); return h; } /** * Generates hash codes for an array of bytes up to the specified length. It is suggested to cache * the hash code. * * @param v the array * @param len the maximal length to generate hashCode * @return the hash code */ public static final int hashCode(byte[] v, int len) { int h = 1; // not to return 0 if possible, so caller cache it easily if (len > v.length) len = v.length; for (int j = len; --j >= 0; ) h = nextHashCode(h, v[j]); return h; } /** * Generates hash codes for an array. It is suggested to cache the hash code. * * @param v the array * @return the hash code */ public static final int hashCode(char[] v) { int h = 1; // not to return 0 if possible, so caller cache it easily for (int j = v.length; --j >= 0; ) h = nextHashCode(h, v[j]); return h; } /** * Generates hash codes for an array. It is suggested to cache the hash code. * * @param v the array * @return the hash code */ public static final int hashCode(short[] v) { int h = 1; // not to return 0 if possible, so caller cache it easily for (int j = v.length; --j >= 0; ) h = nextHashCode(h, v[j]); return h; } /** * Generates hash codes for an array. It is suggested to cache the hash code. * * @param v the byte array * @return the hash code */ public static final int hashCode(int[] v) { int h = 1; // not to return 0 if possible, so caller cache it easily for (int j = v.length; --j >= 0; ) h = nextHashCode(h, v[j]); return h; } /** * Generates hash codes for an array. It is suggested to cache the hash code. * * @param v the array * @return the hash code */ public static final int hashCode(long[] v) { int h = 1; // not to return 0 if possible, so caller cache it easily for (int j = v.length; --j >= 0; ) { h = nextHashCode(h, (int) v[j]); h = nextHashCode(h, (int) (v[j] >> 32)); } return h; } /** Returns the object's hash code, or zero if null. */ public static final int hashCode(Object o) { return o == null ? 0 : o.hashCode(); } /** * Tests whether two objects are equals. * * <p>It takes care of the null case. Thus, it is helpful to implement Object.equals. * * <p>Notice: it uses compareTo if BigDecimal is found. So, in this case, a.equals(b) might not be * the same as Objects.equals(a, b). * * <p>If both a and b are Object[], they are compared item-by-item. */ public static final boolean equals(Object a, Object b) { if (a == b || (a != null && b != null && a.equals(b))) return true; if ((a instanceof BigDecimal) && (b instanceof BigDecimal)) return ((BigDecimal) a).compareTo((BigDecimal) b) == 0; if (a == null || !a.getClass().isArray()) return false; if ((a instanceof Object[]) && (b instanceof Object[])) { final Object[] as = (Object[]) a; final Object[] bs = (Object[]) b; if (as.length != bs.length) return false; for (int j = as.length; --j >= 0; ) if (!equals(as[j], bs[j])) // recursive return false; return true; } if ((a instanceof int[]) && (b instanceof int[])) { final int[] as = (int[]) a; final int[] bs = (int[]) b; if (as.length != bs.length) return false; for (int j = as.length; --j >= 0; ) if (as[j] != bs[j]) return false; return true; } if ((a instanceof byte[]) && (b instanceof byte[])) { final byte[] as = (byte[]) a; final byte[] bs = (byte[]) b; if (as.length != bs.length) return false; for (int j = as.length; --j >= 0; ) if (as[j] != bs[j]) return false; return true; } if ((a instanceof char[]) && (b instanceof char[])) { final char[] as = (char[]) a; final char[] bs = (char[]) b; if (as.length != bs.length) return false; for (int j = as.length; --j >= 0; ) if (as[j] != bs[j]) return false; return true; } if ((a instanceof long[]) && (b instanceof long[])) { final long[] as = (long[]) a; final long[] bs = (long[]) b; if (as.length != bs.length) return false; for (int j = as.length; --j >= 0; ) if (as[j] != bs[j]) return false; return true; } if ((a instanceof short[]) && (b instanceof short[])) { final short[] as = (short[]) a; final short[] bs = (short[]) b; if (as.length != bs.length) return false; for (int j = as.length; --j >= 0; ) if (as[j] != bs[j]) return false; return true; } if ((a instanceof double[]) && (b instanceof double[])) { final double[] as = (double[]) a; final double[] bs = (double[]) b; if (as.length != bs.length) return false; for (int j = as.length; --j >= 0; ) if (as[j] != bs[j]) return false; return true; } if ((a instanceof float[]) && (b instanceof float[])) { final float[] as = (float[]) a; final float[] bs = (float[]) b; if (as.length != bs.length) return false; for (int j = as.length; --j >= 0; ) if (as[j] != bs[j]) return false; return true; } if ((a instanceof boolean[]) && (b instanceof boolean[])) { final boolean[] as = (boolean[]) a; final boolean[] bs = (boolean[]) b; if (as.length != bs.length) return false; for (int j = as.length; --j >= 0; ) if (as[j] != bs[j]) return false; return true; } return false; } /** * Converts any object to a character array. * * @param o the object to convert * @return the char array or null if o is null */ public static final char[] toCharArray(Object o) { if (o == null) return null; if (o instanceof char[]) return (char[]) o; if (o instanceof String) return ((String) o).toCharArray(); try { Method m = o.getClass().getMethod("toCharArray"); return (char[]) m.invoke(o); } catch (Exception ex) { return (o.toString()).toCharArray(); } } /** * Converts any object to a string. If o is an object array, it invokes {@link ArraysX#toString} * to make the string more readable. */ public static final String toString(Object o) { if (o == null) return null; if (o instanceof Date) return DateFormats.format((Date) o, false); if (o instanceof Class) { final Class cls = (Class) o; final String clsnm = cls.getName(); if (!clsnm.startsWith("$Proxy")) return "class " + clsnm; final Class[] ifs = cls.getInterfaces(); switch (ifs.length) { case 0: return "class " + clsnm; case 1: return "proxy " + Objects.toString(ifs[0]); default: return "proxy " + Objects.toString(ifs); } } if (o.getClass().isArray()) { if (o instanceof Object[]) return ArraysX.toString((Object[]) o); if (o instanceof int[]) return ArraysX.toString((int[]) o); if (o instanceof short[]) return ArraysX.toString((short[]) o); if (o instanceof long[]) return ArraysX.toString((long[]) o); if (o instanceof double[]) return ArraysX.toString((double[]) o); if (o instanceof byte[]) return ArraysX.toString((byte[]) o); if (o instanceof boolean[]) return ArraysX.toString((boolean[]) o); if (o instanceof char[]) return ArraysX.toString((char[]) o); if (o instanceof float[]) return ArraysX.toString((float[]) o); } return o.toString(); } /** Converts an integer to a big-endian byte array. */ public static final byte[] toByteArray(int v) { return new byte[] {(byte) (v >>> 24), (byte) (v >>> 16), (byte) (v >>> 8), (byte) v}; } /** Converts a long to a big-endian byte array. */ public static final byte[] toByteArray(long v) { return new byte[] { (byte) (v >>> 56), (byte) (v >>> 48), (byte) (v >>> 40), (byte) (v >>> 32), (byte) (v >>> 24), (byte) (v >>> 16), (byte) (v >>> 8), (byte) v }; } /** Converts a short to a big-endian byte array. */ public static final byte[] toByteArray(short v) { return new byte[] {(byte) (v >>> 8), (byte) v}; } /** Converts a byte to a big-endian byte array. */ public static final byte[] toByteArray(byte v) { return new byte[] {v}; } /** * Clones the specified object. Use clone() if Cloeable. Otherwise, try to serialize/deserialize * it by use of MarshalledObject. * * <p>If o is null, null is returned. * * @exception SystemException if failed to clone */ public static final Object clone(Object o) { if (o == null) return o; try { final Class<?> kls = o.getClass(); if (kls.isArray()) return ArraysX.clone(o); if (o instanceof Cloneable) { try { return kls.getMethod("clone").invoke(o); } catch (NoSuchMethodException ex) { if (log.debugable()) log.debug("No clone() for " + kls); } } // :TODO: MarshalledObject is said with very bad performance, change it // if exists other good deep clone method. return new MarshalledObject<Object>(o).get(); } catch (Exception ex) { throw SystemException.Aide.wrap(ex); } } }
public class EmployeeController extends GenericForwardComposer { /** */ private static final long serialVersionUID = -9089818718875587898L; private static final Log log = Log.lookup(EmployeeController.class); private Employee _currentEmployee; private ListModelList _model = EmployeeService.INSTANCE.getModel(); Listbox lstEmployee; Textbox txtFirstName, txtLastName; Intbox intAge; public Employee getCurrentEmployee() { return _currentEmployee; } public void setCurrentEmployee(Employee currentEmployee) { this._currentEmployee = currentEmployee; } public ListModel getEmployeeModel() { return _model; } // click events public void onClick$btnAddEmployee(ForwardEvent fe) { String firstName = txtFirstName.getText(); String lastName = txtLastName.getText(); int iAge = Integer.parseInt(intAge.getText()); Employee employee = new Employee(firstName, lastName, iAge); if (!_model.add(employee)) { reportError(Messages.getString("EmployeeController.0"), employee); } } public void onClick$btnUpdateEmployee(ForwardEvent fe) { if (lstEmployee.getSelectedItem() != null) { Employee employee = (Employee) (lstEmployee.getSelectedItem().getValue()); employee.setFirstName(txtFirstName.getText()); employee.setLastName(txtLastName.getText()); employee.setAge(Integer.parseInt(intAge.getText())); } else { UiUtils.showMessage(Messages.getString("EmployeeController.2")); } } public void onClick$btnDeleteEmployee(ForwardEvent fe) { if (lstEmployee.getSelectedItem() != null) { Employee employee = (Employee) (lstEmployee.getSelectedItem().getValue()); if (!_model.remove(employee)) { reportError(Messages.getString("EmployeeController.3"), employee); } } else { UiUtils.showMessage(Messages.getString("EmployeeController.4")); } } private void reportError(String message, Employee employee) { StringBuilder sb = new StringBuilder(message) .append(Messages.getString("EmployeeController.5")) .append(employee); final String error = sb.toString(); UiUtils.showMessage(error); log.error(error); } }
/** * Utilities relevant to content types. * * @author tomyeh */ public class ContentTypes { private static final Log log = Log.lookup(ContentTypes.class); /** A map of (String format, String contentType). */ private static final Map<String, String> _fmt2ct = new HashMap<String, String>(64); /** A map of (String contentType, String format). */ private static final Map<String, String> _ct2fmt = new HashMap<String, String>(64); protected ContentTypes() {} // prevent from initializing /** * Returns whether the content type is binary. * * @param ctype the content type, e.g., text/plain If ctype is null or empty, false is returned. * @since 3.0.6 */ public static final boolean isBinary(String ctype) { return ctype != null && ctype.length() > 0 && !ctype.startsWith("text/") && ctype.indexOf("script") < 0; } /** * Returns the content type of the specified format, such as "html" and "pdf", or null if not * found (or format is null). */ public static final String getContentType(String format) { if (format == null) return null; format = format.trim().toLowerCase(java.util.Locale.ENGLISH); for (; ; ) { synchronized (_fmt2ct) { String fmt2ct = _fmt2ct.get(format); if (fmt2ct != null) return fmt2ct; } int j = format.indexOf('.'); if (j < 0) return null; format = format.substring(j + 1); } } /** * Returns the format of the specified content type, or null if not found. * * @exception IllealArgumentException if ctype is null */ public static final String getFormat(String ctype) { if (ctype == null) throw new IllegalArgumentException(); ctype = ctype.trim().toLowerCase(java.util.Locale.ENGLISH); String format; synchronized (_ct2fmt) { format = _ct2fmt.get(ctype); } if (format == null) { // sometime, content type is "text/html;charset=UTF-8" int j = ctype.indexOf(';'); if (j >= 0) { ctype = ctype.substring(0, j); synchronized (_ct2fmt) { format = _ct2fmt.get(ctype); } } if (format == null) { j = ctype.indexOf('/'); format = j >= 0 ? ctype.substring(j + 1) : ctype; } } return format; } /** * Adds additional binding of the format and content type. * * <p>You rarely need to invoke this method, unless your format is not by the default mapping. */ public static final void put(String format, String ctype) { if (format == null || ctype == null) throw new NullPointerException("format or ctype"); synchronized (_fmt2ct) { _fmt2ct.put(format, ctype); } synchronized (_ct2fmt) { _ct2fmt.put(ctype, format); } } static { final String flnm = "/metainfo/org/zkoss/util/media/contentTypes.properties"; if (!load(flnm)) log.warning(MCommon.FILE_NOT_FOUND, flnm); load("/contentTypes.properties"); // override! } private static final boolean load(String flnm) { final InputStream strm = ContentTypes.class.getResourceAsStream(flnm); if (strm == null) return false; BufferedReader in = null; // NOTE: we cannot use Properties.load because there might be replicated // mapping (e.g., jpg=images/jpg, jpg=images/jpeg) try { in = new BufferedReader(new InputStreamReader(strm)); String line; while ((line = in.readLine()) != null) { final int j = line.indexOf('='); if (j < 0) { final int k = Strings.skipWhitespaces(line, 0); if (k < line.length() && line.charAt(k) != '#') log.warning("Ignored error; illgal format: " + line); continue; } final String format = line.substring(0, j).trim(); final String ctype = line.substring(j + 1).trim(); if (format.length() == 0 || ctype.length() == 0) { log.warning("Ignored error; illgal format: " + line); continue; } _fmt2ct.put(format, ctype); _ct2fmt.put(ctype, format); } } catch (IOException ex) { log.warning("Ingored error: Unable to read " + flnm, ex); } finally { if (in != null) { try { in.close(); } catch (IOException e) { } } try { strm.close(); } catch (Throwable ex) { } } return true; } }
/** * Contains common importing behavior for both XLSX and XLS. Spreadsheet {@link SBook} model * including following information: Book: name Sheet: name, (default) column width, (default) row * height, hidden row (column), row (column) style, freeze, merge, protection, named range , * gridline display Cell: type, value, font with color and style, type offset(normal or subscript), * background color, border's type and color , data format, alignment, wrap, locked, fill pattern * * <p>We use XLSX, XLS common interface (e.g. CellStyle instead of {@link XSSFCellStyle}) to get * content first for that codes can be easily moved to parent class. * * @author Hawk * @since 3.5.0 */ public abstract class AbstractExcelImporter extends AbstractImporter { private static final Log _logger = Log.lookup(AbstractExcelImporter.class); /** * <poi CellStyle index, {@link SCellStyle} object> Keep track of imported style during importing * to avoid creating duplicated style objects. */ protected Map<CellStyle, SCellStyle> importedStyle = new HashMap<CellStyle, SCellStyle>(); // ZSS-685 /** <poi Font index, {@link SFont} object> * */ protected Map<Short, SFont> importedFont = new HashMap<Short, SFont>(); /** target book model */ protected SBook book; /** source POI book */ protected Workbook workbook; // ZSS-735, poiPictureData -> SPictureData index protected Map<PictureData, Integer> importedPictureData = new HashMap<PictureData, Integer>(); /** book type key for book attribute * */ protected static String BOOK_TYPE_KEY = "$ZSS.BOOKTYPE$"; // ZSS-854 protected void importDefaultCellStyles() { ((AbstractBookAdv) book).clearDefaultCellStyles(); for (CellStyle poiStyle : workbook.getDefaultCellStyles()) { book.addDefaultCellStyle(importCellStyle(poiStyle, false)); } // in case of XLS files which we have not support defaultCellStyles if (book.getDefaultCellStyles().isEmpty()) { ((AbstractBookAdv) book).initDefaultCellStyles(); } // ZSS-1132: setup default font ((AbstractBookAdv) book).initDefaultFont(); } // ZSS-854 protected void importNamedStyles() { ((AbstractBookAdv) book).clearNamedStyles(); for (NamedStyle poiStyle : workbook.getNamedStyles()) { SNamedStyle namedStyle = new NamedStyleImpl( poiStyle.getName(), poiStyle.isCustomBuiltin(), poiStyle.getBuiltinId(), book, poiStyle.getIndex()); book.addNamedCellstyle(namedStyle); } } /** * Import the model according to reversed dependency order among model objects: book, sheet, * defined name, cells, chart, pictures, validation. */ @Override public SBook imports(InputStream is, String bookName) throws IOException { // clear cache for reuse importedStyle.clear(); importedFont.clear(); workbook = createPoiBook(is); book = SBooks.createBook(bookName); // book.setDefaultCellStyle(importCellStyle(workbook.getCellStyleAt((short) 0), false)); // //ZSS-780 // ZSS-854 importDefaultCellStyles(); importNamedStyles(); // ZSS-1140 importExtraStyles(); setBookType(book); // ZSS-715: Enforce internal Locale.US Locale so formula is in consistent internal format Locale old = Locales.setThreadLocal(Locale.US); SBookSeries bookSeries = book.getBookSeries(); boolean isCacheClean = bookSeries.isAutoFormulaCacheClean(); try { bookSeries.setAutoFormulaCacheClean(false); // disable it to avoid // unnecessary clean up // during importing importExternalBookLinks(); int numberOfSheet = workbook.getNumberOfSheets(); for (int i = 0; i < numberOfSheet; i++) { Sheet poiSheet = workbook.getSheetAt(i); importSheet(poiSheet, i); SSheet sheet = book.getSheet(i); importTables(poiSheet, sheet); // ZSS-855, ZSS-1011 } importNamedRange(); for (int i = 0; i < numberOfSheet; i++) { SSheet sheet = book.getSheet(i); Sheet poiSheet = workbook.getSheetAt(i); for (Row poiRow : poiSheet) { importRow(poiRow, sheet); } importColumn(poiSheet, sheet); importMergedRegions(poiSheet, sheet); importDrawings(poiSheet, sheet); importValidation(poiSheet, sheet); importAutoFilter(poiSheet, sheet); importSheetProtection(poiSheet, sheet); // ZSS-576 } } finally { book.getBookSeries().setAutoFormulaCacheClean(isCacheClean); Locales.setThreadLocal(old); } return book; } protected abstract Workbook createPoiBook(InputStream is) throws IOException; protected abstract void setBookType(SBook book); /** * Gets the book-type information ("xls" or "xlsx"), return null if not found * * @param book * @return */ public static String getBookType(SBook book) { return (String) book.getAttribute(BOOK_TYPE_KEY); } /** * When a column is hidden with default width, we don't import the width for it's 0. We also don't * import the width that equals to default width for optimization. * * @param poiSheet * @param sheet */ protected abstract void importColumn(Sheet poiSheet, SSheet sheet); /** * If in same column: anchorWidthInFirstColumn + anchor width in inter-columns + * anchorWidthInLastColumn (dx2) no in same column: anchorWidthInLastColumn - offsetInFirstColumn * (dx1) */ protected abstract int getAnchorWidthInPx(ClientAnchor anchor, Sheet poiSheet); protected abstract int getAnchorHeightInPx(ClientAnchor anchor, Sheet poiSheet); /** * Name should be created after sheets created. A special defined name, _xlnm._FilterDatabase * (xlsx) or _FilterDatabase (xls), stores the selected cells for auto-filter */ protected void importNamedRange() { for (int i = 0; i < workbook.getNumberOfNames(); i++) { Name definedName = workbook.getNameAt(i); if (skipName(definedName)) { continue; } SName namedRange = null; if (definedName.getSheetIndex() == -1) { // workbook scope namedRange = book.createName(definedName.getNameName()); } else { namedRange = book.createName(definedName.getNameName(), definedName.getSheetName()); } namedRange.setRefersToFormula(definedName.getRefersToFormula()); } } protected boolean skipName(Name definedName) { String namename = definedName.getNameName(); if (namename == null) { return true; } // ignore defined name of functions, they are macro functions that we don't support if (definedName.isFunctionName()) { return true; } if (definedName.getRefersToFormula() == null) { // ignore defined name with null formula, don't know when will have this case return true; } return false; } /** * Excel uses external book links to map external book index and name. The formula contains full * external book name or index only (e.g [book2.xlsx] or [1]). We needs such table for parsing and * evaluating formula when necessary. */ protected abstract void importExternalBookLinks(); // ZSS-952 protected void importSheetDefaultColumnWidth(Sheet poiSheet, SSheet sheet) { // reference XUtils.getDefaultColumnWidthInPx() int defaultWidth = UnitUtil.defaultColumnWidthToPx( poiSheet.getDefaultColumnWidth(), ((AbstractBookAdv) book).getCharWidth()); // ZSS-1132 sheet.setDefaultColumnWidth(defaultWidth); } /* * import sheet scope content from POI Sheet. */ protected SSheet importSheet(Sheet poiSheet, int poiSheetIndex) { SSheet sheet = book.createSheet(poiSheet.getSheetName()); sheet.setDefaultRowHeight(UnitUtil.twipToPx(poiSheet.getDefaultRowHeight())); // ZSS-952 importSheetDefaultColumnWidth(poiSheet, sheet); // reference FreezeInfoLoaderImpl.getRowFreeze() sheet.getViewInfo().setNumOfRowFreeze(BookHelper.getRowFreeze(poiSheet)); sheet.getViewInfo().setNumOfColumnFreeze(BookHelper.getColumnFreeze(poiSheet)); sheet .getViewInfo() .setDisplayGridlines( poiSheet .isDisplayGridlines()); // Note isDisplayGridlines() and isPrintGridlines() are // different sheet.getViewInfo().setColumnBreaks(poiSheet.getColumnBreaks()); sheet.getViewInfo().setRowBreaks(poiSheet.getRowBreaks()); SPrintSetup sps = sheet.getPrintSetup(); SHeader header = sheet.getViewInfo().getHeader(); if (header != null) { header.setCenterText(poiSheet.getHeader().getCenter()); header.setLeftText(poiSheet.getHeader().getLeft()); header.setRightText(poiSheet.getHeader().getRight()); sps.setHeader(header); } SFooter footer = sheet.getViewInfo().getFooter(); if (footer != null) { footer.setCenterText(poiSheet.getFooter().getCenter()); footer.setLeftText(poiSheet.getFooter().getLeft()); footer.setRightText(poiSheet.getFooter().getRight()); sps.setFooter(footer); } if (poiSheet.isDiffOddEven()) { Header poiEvenHeader = poiSheet.getEvenHeader(); if (poiEvenHeader != null) { SHeader evenHeader = new HeaderFooterImpl(); evenHeader.setCenterText(poiEvenHeader.getCenter()); evenHeader.setLeftText(poiEvenHeader.getLeft()); evenHeader.setRightText(poiEvenHeader.getRight()); sps.setEvenHeader(evenHeader); } Footer poiEvenFooter = poiSheet.getEvenFooter(); if (poiEvenFooter != null) { SFooter evenFooter = new HeaderFooterImpl(); evenFooter.setCenterText(poiEvenFooter.getCenter()); evenFooter.setLeftText(poiEvenFooter.getLeft()); evenFooter.setRightText(poiEvenFooter.getRight()); sps.setEvenFooter(evenFooter); } } if (poiSheet.isDiffFirst()) { Header poiFirstHeader = poiSheet.getFirstHeader(); if (poiFirstHeader != null) { SHeader firstHeader = new HeaderFooterImpl(); firstHeader.setCenterText(poiFirstHeader.getCenter()); firstHeader.setLeftText(poiFirstHeader.getLeft()); firstHeader.setRightText(poiFirstHeader.getRight()); sps.setFirstHeader(firstHeader); } Footer poiFirstFooter = poiSheet.getFirstFooter(); if (poiFirstFooter != null) { SFooter firstFooter = new HeaderFooterImpl(); firstFooter.setCenterText(poiFirstFooter.getCenter()); firstFooter.setLeftText(poiFirstFooter.getLeft()); firstFooter.setRightText(poiFirstFooter.getRight()); sps.setFirstFooter(firstFooter); } } PrintSetup poips = poiSheet.getPrintSetup(); sps.setBottomMargin(poiSheet.getMargin(Sheet.BottomMargin)); sps.setTopMargin(poiSheet.getMargin(Sheet.TopMargin)); sps.setLeftMargin(poiSheet.getMargin(Sheet.LeftMargin)); sps.setRightMargin(poiSheet.getMargin(Sheet.RightMargin)); sps.setHeaderMargin(poiSheet.getMargin(Sheet.HeaderMargin)); sps.setFooterMargin(poiSheet.getMargin(Sheet.FooterMargin)); sps.setAlignWithMargins(poiSheet.isAlignMargins()); sps.setErrorPrintMode(poips.getErrorsMode()); sps.setFitHeight(poips.getFitHeight()); sps.setFitWidth(poips.getFitWidth()); sps.setHCenter(poiSheet.getHorizontallyCenter()); sps.setLandscape(poips.getLandscape()); sps.setLeftToRight(poips.getLeftToRight()); sps.setPageStart(poips.getUsePage() ? poips.getPageStart() : 0); sps.setPaperSize(PoiEnumConversion.toPaperSize(poips.getPaperSize())); sps.setCommentsMode(poips.getCommentsMode()); sps.setPrintGridlines(poiSheet.isPrintGridlines()); sps.setPrintHeadings(poiSheet.isPrintHeadings()); sps.setScale(poips.getScale()); sps.setScaleWithDoc(poiSheet.isScaleWithDoc()); sps.setDifferentOddEvenPage(poiSheet.isDiffOddEven()); sps.setDifferentFirstPage(poiSheet.isDiffFirst()); sps.setVCenter(poiSheet.getVerticallyCenter()); Workbook poiBook = poiSheet.getWorkbook(); String area = poiBook.getPrintArea(poiSheetIndex); if (area != null) { sps.setPrintArea(area); } CellRangeAddress rowrng = poiSheet.getRepeatingRows(); if (rowrng != null) { sps.setRepeatingRowsTitle(rowrng.getFirstRow(), rowrng.getLastRow()); } CellRangeAddress colrng = poiSheet.getRepeatingColumns(); if (colrng != null) { sps.setRepeatingColumnsTitle(colrng.getFirstColumn(), colrng.getLastColumn()); } sheet.setPassword(poiSheet.getProtect() ? "" : null); // import hashed password directly importPassword(poiSheet, sheet); // ZSS-832 // import sheet visible if (poiBook.isSheetHidden(poiSheetIndex)) { sheet.setSheetVisible(SheetVisible.HIDDEN); } else if (poiBook.isSheetVeryHidden(poiSheetIndex)) { sheet.setSheetVisible(SheetVisible.VERY_HIDDEN); } else { sheet.setSheetVisible(SheetVisible.VISIBLE); } // ZSS-1130 // import conditionalFormatting importConditionalFormatting(sheet, poiSheet); return sheet; } protected abstract void importPassword(Sheet poiSheet, SSheet sheet); protected void importMergedRegions(Sheet poiSheet, SSheet sheet) { // merged cells // reference RangeImpl.getMergeAreas() int nMerged = poiSheet.getNumMergedRegions(); final SheetImpl sheetImpl = (SheetImpl) sheet; for (int i = nMerged - 1; i >= 0; --i) { final CellRangeAddress mergedRegion = poiSheet.getMergedRegion(i); // ZSS-1114: any new merged region that overlapped with previous merged region is thrown away final CellRegion r = new CellRegion( mergedRegion.getFirstRow(), mergedRegion.getFirstColumn(), mergedRegion.getLastRow(), mergedRegion.getLastColumn()); final CellRegion overlapped = sheetImpl.checkMergedRegion(r); if (overlapped != null) { _logger.warning( "Drop the region " + r + " which is overlapped with existing merged area " + overlapped + "."); continue; } sheetImpl.addDirectlyMergedRegion(r); } } /** Drawings includes charts and pictures. */ protected abstract void importDrawings(Sheet poiSheet, SSheet sheet); protected abstract void importValidation(Sheet poiSheet, SSheet sheet); protected SRow importRow(Row poiRow, SSheet sheet) { SRow row = sheet.getRow(poiRow.getRowNum()); row.setHeight(UnitUtil.twipToPx(poiRow.getHeight())); row.setCustomHeight(poiRow.isCustomHeight()); row.setHidden(poiRow.getZeroHeight()); CellStyle rowStyle = poiRow.getRowStyle(); if (rowStyle != null) { row.setCellStyle(importCellStyle(rowStyle)); } for (Cell poiCell : poiRow) { importCell(poiCell, poiRow.getRowNum(), sheet); } return row; } protected SCell importCell(Cell poiCell, int row, SSheet sheet) { SCell cell = sheet.getCell(row, poiCell.getColumnIndex()); cell.setCellStyle(importCellStyle(poiCell.getCellStyle())); switch (poiCell.getCellType()) { case Cell.CELL_TYPE_NUMERIC: cell.setNumberValue(poiCell.getNumericCellValue()); break; case Cell.CELL_TYPE_STRING: RichTextString poiRichTextString = poiCell.getRichStringCellValue(); if (poiRichTextString != null && poiRichTextString.numFormattingRuns() > 0) { SRichText richText = cell.setupRichTextValue(); importRichText(poiCell, poiRichTextString, richText); } else { cell.setStringValue(poiCell.getStringCellValue()); } break; case Cell.CELL_TYPE_BOOLEAN: cell.setBooleanValue(poiCell.getBooleanCellValue()); break; case Cell.CELL_TYPE_FORMULA: cell.setFormulaValue(poiCell.getCellFormula()); // ZSS-873 if (isImportCache() && !poiCell.isCalcOnLoad() && !mustCalc(cell)) { ValueEval val = null; switch (poiCell.getCachedFormulaResultType()) { case Cell.CELL_TYPE_NUMERIC: val = new NumberEval(poiCell.getNumericCellValue()); break; case Cell.CELL_TYPE_STRING: RichTextString poiRichTextString0 = poiCell.getRichStringCellValue(); if (poiRichTextString0 != null && poiRichTextString0.numFormattingRuns() > 0) { SRichText richText = new RichTextImpl(); importRichText(poiCell, poiRichTextString0, richText); val = new StringEval(richText.getText()); } else { val = new StringEval(poiCell.getStringCellValue()); } break; case Cell.CELL_TYPE_BOOLEAN: val = BoolEval.valueOf(poiCell.getBooleanCellValue()); break; case Cell.CELL_TYPE_ERROR: val = ErrorEval.valueOf(poiCell.getErrorCellValue()); break; case Cell.CELL_TYPE_BLANK: default: // do nothing } if (val != null) { ((AbstractCellAdv) cell).setFormulaResultValue(val); } } break; case Cell.CELL_TYPE_ERROR: cell.setErrorValue(PoiEnumConversion.toErrorCode(poiCell.getErrorCellValue())); break; case Cell.CELL_TYPE_BLANK: // do nothing because spreadsheet model auto creates blank cells default: // TODO log: leave an unknown cell type as a blank cell. break; } Hyperlink poiHyperlink = poiCell.getHyperlink(); if (poiHyperlink != null) { String addr = poiHyperlink.getAddress(); String label = poiHyperlink.getLabel(); SHyperlink hyperlink = cell.setupHyperlink( PoiEnumConversion.toHyperlinkType(poiHyperlink.getType()), addr == null ? "" : addr, label == null ? "" : label); cell.setHyperlink(hyperlink); } Comment poiComment = poiCell.getCellComment(); if (poiComment != null) { SComment comment = cell.setupComment(); comment.setAuthor(poiComment.getAuthor()); comment.setVisible(poiComment.isVisible()); RichTextString poiRichTextString = poiComment.getString(); if (poiRichTextString != null && poiRichTextString.numFormattingRuns() > 0) { importRichText(poiCell, poiComment.getString(), comment.setupRichText()); } else { comment.setText(poiComment.toString()); } } return cell; } protected void importRichText( Cell poiCell, RichTextString poiRichTextString, SRichText richText) { String cellValue = poiRichTextString.getString(); // ZSS-1138 int count = poiRichTextString.numFormattingRuns(); if (count <= 0) { richText.addSegment(cellValue, null); // ZSS-1138: null font means use cell's font } else { // ZSS-1138 int prevFormattingRunIndex = poiRichTextString.getIndexOfFormattingRun(0); if (prevFormattingRunIndex > 0) { final String content = cellValue.substring(0, prevFormattingRunIndex); richText.addSegment(content, null); // ZSS-1138: null font means use cell's font } for (int i = 0; i < count; i++) { int nextFormattingRunIndex = (i + 1) >= poiRichTextString.numFormattingRuns() ? cellValue.length() : poiRichTextString.getIndexOfFormattingRun(i + 1); final String content = cellValue.substring(prevFormattingRunIndex, nextFormattingRunIndex); richText.addSegment( content, toZssFont(getPoiFontFromRichText(workbook, poiCell, poiRichTextString, i))); prevFormattingRunIndex = nextFormattingRunIndex; } } } /** * Convert CellStyle into NCellStyle * * @param poiCellStyle */ protected SCellStyle importCellStyle(CellStyle poiCellStyle) { return importCellStyle(poiCellStyle, true); } protected SCellStyle importCellStyle(CellStyle poiCellStyle, boolean inStyleTable) { SCellStyle cellStyle = null; // short idx = poiCellStyle.getIndex(); // ZSS-685 if ((cellStyle = importedStyle.get(poiCellStyle)) == null) { // ZSS-685 cellStyle = book.createCellStyle(inStyleTable); importedStyle.put(poiCellStyle, cellStyle); // ZSS-685 String dataFormat = poiCellStyle.getRawDataFormatString(); if (dataFormat == null) { // just in case dataFormat = SCellStyle.FORMAT_GENERAL; } if (!poiCellStyle.isBuiltinDataFormat()) { cellStyle.setDirectDataFormat(dataFormat); } else { cellStyle.setDataFormat(dataFormat); } cellStyle.setWrapText(poiCellStyle.getWrapText()); cellStyle.setLocked(poiCellStyle.getLocked()); cellStyle.setAlignment(PoiEnumConversion.toHorizontalAlignment(poiCellStyle.getAlignment())); cellStyle.setVerticalAlignment( PoiEnumConversion.toVerticalAlignment(poiCellStyle.getVerticalAlignment())); cellStyle.setRotation(poiCellStyle.getRotation()); // ZSS-918 cellStyle.setIndention(poiCellStyle.getIndention()); // ZSS-915 Color fgColor = poiCellStyle.getFillForegroundColorColor(); Color bgColor = poiCellStyle.getFillBackgroundColorColor(); // if (fgColor == null && bgColor != null) { //ZSS-797 // fgColor = bgColor; // } // ZSS-857: SOLID pattern: switch fillColor and backColor cellStyle.setFillPattern(PoiEnumConversion.toFillPattern(poiCellStyle.getFillPattern())); SColor fgSColor = book.createColor(BookHelper.colorToForegroundHTML(workbook, fgColor)); SColor bgSColor = book.createColor(BookHelper.colorToBackgroundHTML(workbook, bgColor)); if (cellStyle.getFillPattern() == FillPattern.SOLID) { SColor tmp = fgSColor; fgSColor = bgSColor; bgSColor = tmp; } cellStyle.setFillColor(fgSColor); cellStyle.setBackColor(bgSColor); // ZSS-780 cellStyle.setBorderLeft(PoiEnumConversion.toBorderType(poiCellStyle.getBorderLeft())); cellStyle.setBorderTop(PoiEnumConversion.toBorderType(poiCellStyle.getBorderTop())); cellStyle.setBorderRight(PoiEnumConversion.toBorderType(poiCellStyle.getBorderRight())); cellStyle.setBorderBottom(PoiEnumConversion.toBorderType(poiCellStyle.getBorderBottom())); cellStyle.setBorderLeftColor( book.createColor( BookHelper.colorToBorderHTML(workbook, poiCellStyle.getLeftBorderColorColor()))); cellStyle.setBorderTopColor( book.createColor( BookHelper.colorToBorderHTML(workbook, poiCellStyle.getTopBorderColorColor()))); cellStyle.setBorderRightColor( book.createColor( BookHelper.colorToBorderHTML(workbook, poiCellStyle.getRightBorderColorColor()))); cellStyle.setBorderBottomColor( book.createColor( BookHelper.colorToBorderHTML(workbook, poiCellStyle.getBottomBorderColorColor()))); cellStyle.setHidden(poiCellStyle.getHidden()); // same style always use same font cellStyle.setFont(importFont(poiCellStyle)); } return cellStyle; } protected SFont importFont(CellStyle poiCellStyle) { SFont font = null; final short fontIndex = poiCellStyle.getFontIndex(); if (importedFont.containsKey(fontIndex)) { font = importedFont.get(fontIndex); } else { Font poiFont = workbook.getFontAt(fontIndex); font = createZssFont(poiFont); importedFont.put(fontIndex, font); // ZSS-677 } return font; } protected SFont toZssFont(Font poiFont) { if (poiFont == null) return null; // ZSS-1138 SFont font = null; final short fontIndex = poiFont.getIndex(); if (importedFont.containsKey(fontIndex)) { font = importedFont.get(fontIndex); } else { font = createZssFont(poiFont); importedFont.put(fontIndex, font); // ZSS-677 } return font; } protected SFont createZssFont(Font poiFont) { SFont font = book.createFont(true); // font font.setName(poiFont.getFontName()); font.setBoldweight(PoiEnumConversion.toBoldweight(poiFont.getBoldweight())); font.setItalic(poiFont.getItalic()); font.setStrikeout(poiFont.getStrikeout()); font.setUnderline(PoiEnumConversion.toUnderline(poiFont.getUnderline())); font.setHeightPoints(poiFont.getFontHeightInPoints()); font.setTypeOffset(PoiEnumConversion.toTypeOffset(poiFont.getTypeOffset())); font.setColor(book.createColor(BookHelper.getFontHTMLColor(workbook, poiFont))); return font; } protected ViewAnchor toViewAnchor(Sheet poiSheet, ClientAnchor clientAnchor) { int width = getAnchorWidthInPx(clientAnchor, poiSheet); int height = getAnchorHeightInPx(clientAnchor, poiSheet); ViewAnchor viewAnchor = new ViewAnchor(clientAnchor.getRow1(), clientAnchor.getCol1(), width, height); viewAnchor.setXOffset(getXoffsetInPixel(clientAnchor, poiSheet)); viewAnchor.setYOffset(getYoffsetInPixel(clientAnchor, poiSheet)); return viewAnchor; } protected abstract int getXoffsetInPixel(ClientAnchor clientAnchor, Sheet poiSheet); protected abstract int getYoffsetInPixel(ClientAnchor clientAnchor, Sheet poiSheet); protected void importPicture(List<Picture> poiPictures, Sheet poiSheet, SSheet sheet) { for (Picture poiPicture : poiPictures) { PictureData poiPicData = poiPicture.getPictureData(); Integer picDataIx = importedPictureData.get(poiPicData); // ZSS-735 if (picDataIx != null) { sheet.addPicture( picDataIx.intValue(), toViewAnchor(poiSheet, poiPicture.getClientAnchor())); } else { Format format = Format.valueOfFileExtension(poiPicData.suggestFileExtension()); if (format != null) { SPicture pic = sheet.addPicture( format, poiPicData.getData(), toViewAnchor(poiSheet, poiPicture.getClientAnchor())); importedPictureData.put(poiPicData, pic.getPictureData().getIndex()); } else { // TODO log we ignore a picture with unsupported format } } } } /** * POI AutoFilter.getFilterColumn(i) sometimes returns null. A POI FilterColumn object only exists * when we have set a criteria on that column. For example, if we enable auto filter on 2 columns, * but we only set criteria on 2nd column. Thus, the size of filter column is 1. There is only one * FilterColumn object and its column id is 1. Only getFilterColumn(1) will return a FilterColumn, * other get null. * * @param poiSheet source POI sheet * @param sheet destination sheet */ protected void importAutoFilter(Sheet poiSheet, SSheet sheet) { AutoFilter poiAutoFilter = poiSheet.getAutoFilter(); if (poiAutoFilter != null) { CellRangeAddress filteringRange = poiAutoFilter.getRangeAddress(); SAutoFilter autoFilter = sheet.createAutoFilter(new CellRegion(filteringRange.formatAsString())); int numberOfColumn = filteringRange.getLastColumn() - filteringRange.getFirstColumn() + 1; importAutoFilterColumns(poiAutoFilter, autoFilter, numberOfColumn); // ZSS-1019 } } // ZSS-1019 protected void importAutoFilterColumns( AutoFilter poiFilter, SAutoFilter zssFilter, int numberOfColumn) { for (int i = 0; i < numberOfColumn; i++) { FilterColumn srcColumn = poiFilter.getFilterColumn(i); if (srcColumn == null) { continue; } NFilterColumn destColumn = zssFilter.getFilterColumn(i, true); destColumn.setProperties( PoiEnumConversion.toFilterOperator(srcColumn.getOperator()), srcColumn.getCriteria1(), srcColumn.getCriteria2(), srcColumn.isOn()); } } protected org.zkoss.poi.ss.usermodel.Font getPoiFontFromRichText( Workbook book, Cell cell, RichTextString rstr, int run) { if (run < 0) return null; // ZSS-1138 org.zkoss.poi.ss.usermodel.Font font = rstr instanceof HSSFRichTextString ? book.getFontAt(((HSSFRichTextString) rstr).getFontOfFormattingRun(run)) : ((XSSFRichTextString) rstr).getFontOfFormattingRun((XSSFWorkbook) book, run); if (font == null) { CellStyle style = cell.getCellStyle(); short fontIndex = style != null ? style.getFontIndex() : (short) 0; return book.getFontAt(fontIndex); } return font; } /** * POI SheetProtection. * * @param poiSheet source POI sheet * @param sheet destination sheet */ protected abstract void importSheetProtection(Sheet poiSheet, SSheet sheet); // ZSS-576 /** * POI sheet tables * * @param poiSheet source POI sheet * @param sheet destination sheet * @since 3.8.0 */ protected abstract void importTables(Sheet poiSheet, SSheet sheet); // ZSS-855 // ZSS-873: Import formula cache result from an Excel file protected boolean _importCache = false; /** * Set if import Excel cached value. * * @since 3.7.0 */ public void setImportCache(boolean b) { _importCache = b; } /** * Returns if import file cached value. * * @since 3.7.0 */ protected boolean isImportCache() { return _importCache; } // ZSS-873 // Must evaluate INDIRECT() function to make the dependency table) // Issue845Test-checkIndirectNameRange protected boolean mustCalc(SCell cell) { FormulaExpression val = ((AbstractCellAdv) cell).getFormulaExpression(); for (Ptg ptg : val.getPtgs()) { if (ptg instanceof FuncVarPtg && ((FuncVarPtg) ptg).getFunctionIndex() == 148) { // 148 is INDIRECT return true; } } return false; } // ZSS-1130 protected abstract void importConditionalFormatting(SSheet sheet, Sheet poiSheet); // ZSS-1140 protected void importExtraStyles() { // do nothing; ExcelXlsxImporter should override it } }
/** * The message manager. This class manages how an message is retrieved based on the message code and * the locale. * * <p>Note: unlike MessageFormat's default behavior, all null objects are treated as an empty string * rather than "null". * * @author tomyeh */ public class Messages implements MessageConst { private static final Log log = Log.lookup(Messages.class); private static Formatter _formatter; protected Messages() { // prohibit from inited } /** * Gets a message based on the specified code without formating arugments. * * <p>Equivalent to get(code, null). */ public static final String get(int code) { return get(code, null, getLocale()); } /** Gets a message based on the locale of current user with ONE format-argument. */ public static final String get(int code, Object fmtArg) { return get(code, new Object[] {fmtArg}, getLocale()); } /** * Gets a message based on the locale of current user. * * <p>Equivalent to get(code, fmtArgs, current_locale). The current_locale argument depends on the * implementation. */ public static final String get(int code, Object[] fmtArgs) { return get(code, fmtArgs, getLocale()); } private static final Locale getLocale() { return Locales.getCurrent(); } /** * Gets a message from the resource bundle. * * @return null if no found */ private static final String getFromBundle(int code, Locale locale) { final BundleInfo bi = Aide.getBundleInfo(code); final PropertyBundle rb = // case insensitive PropertyBundle.getBundle(bi.filename, locale, true); if (rb != null) return rb.getProperty(Integer.toHexString(code - getType(code))); throw new IllegalStateException("Missing resource: " + bi + " locale=" + locale); } private static final String getNotFound(int code, Locale locale) { if (code == NULL_CODE) return ""; // special code try { log.error( "Message code not found: " + Integer.toHexString(code) + " not in " + locale + ":" + Aide.getBundleInfo(code)); final String hexcode = Integer.toHexString(code); final String s = getFromBundle(MCommon.MESSAGE_CODE_NOT_FOUND, locale); return s != null ? MessageFormats.format(s, new Object[] {hexcode}, locale) : "Unknown message code: " + hexcode; } catch (Exception ex) { log.realCauseBriefly(ex); return "Unknown message code: " + Integer.toHexString(code); } } /** * Gets a message based on the specified code. If not found, returns an error message to denote * it. * * <p>If fmtArgs is not null, {@link org.zkoss.text.MessageFormats#format} is called to format the * message. However, unlike MessageFormat's default behavior, all null objects are treated as an * empty string rather than "null". * * <p>It also recognizes {@link org.zkoss.lang.Objects#UNKNOWN}. * * @param code the code * @param fmtArgs the argument lists to format the message * @param locale the locale of the message to load * @return the message; never be null */ public static String get(int code, Object[] fmtArgs, Locale locale) { try { String s = getFromBundle(code, locale); if (s == null) return getNotFound(code, locale); if (fmtArgs != null && fmtArgs.length > 0) { final Object[] args = new Object[fmtArgs.length]; final Formatter formatter = _formatter; for (int j = 0; j < fmtArgs.length; ++j) { final Object arg = fmtArgs[j]; if (formatter != null) args[j] = formatter.format(arg); else if (arg == null || arg == Objects.UNKNOWN) args[j] = ""; else if (arg instanceof Object[]) args[j] = Objects.toString(arg); else args[j] = arg; } s = MessageFormats.format(s, args, locale); } return s; } catch (Exception ex) { log.realCause(ex); return getNotFound(code, locale); } } /** Returns the formatter used by {@link #get(int, Object[], Locale)}, or null if not set. */ public static Formatter getFormatter() { return _formatter; } /** * Sets the formatter used by {@link #get(int, Object[], Locale)}. * * <p>Default: null. */ public static void setFormatter(Formatter fmt) { _formatter = fmt; } /** Gets the message type of the specified code. */ public static final int getType(int code) { return code & 0xffff0000; } /** The formatter used by {@link #get(int, Object[], Locale)} to format the specified object. */ public static interface Formatter { /** Formats the specified object into a string. */ public Object format(Object o); } }
/** * The label loader (implementation only). * * <p>Used to implement {@link org.zkoss.util.resource.Labels}. * * <p>Notice that the encoding of the Locale dependent file (*.properties) is assumed to be UTF-8. * If it is not the case, please refer to <a * href="http://books.zkoss.org/wiki/ZK_Configuration_Reference/zk.xml/The_library-property_Element/Library_Properties#org.zkoss.util.label.web.charset">ZK * Configuration Reference</a> for more information. * * @author tomyeh */ public class LabelLoader { private static final Log log = Log.lookup(LabelLoader.class); /** * A map of (Locale l, Map(String key, ExValue label)). We use two maps to speed up the access of * labels. _labels allows concurrent access without synchronization. _syncLabels requires * synchronization and used for update. */ private Map _labels = Collections.EMPTY_MAP; /** * A map of (Locale 1, Map<String key1, Map<String key2...> or ExValue label>) It is used by * variable resolver and allows ${labels.key1.key2}. _segLabels allows concurrent access without * synchronization. See also {@link #getSegmentedLabels}. */ private Map _segLabels = Collections.EMPTY_MAP; /** Map<Locale, Map<String, String>>. */ private final Map _syncLabels = new HashMap(8); /** A set of LabelLocator or LabelLocator2. */ private final Set _locators = new LinkedHashSet(4); // order is important /** The XEL context. */ private final SimpleXelContext _xelc; private String _jarcharset, _warcharset; private final ExpressionFactory _expf; private final FilterMap.Filter _fmfilter; public LabelLoader() { _fmfilter = new FilterMap.Filter() { public Object filter(Object key, Object value) { return value instanceof ExValue ? ((ExValue) value).getValue() : value; } }; _expf = Expressions.newExpressionFactory(); _xelc = new SimpleXelContext(new Resolver(), null); } /** * Returns the label of the specified key for the current locale, or null if not found. * * @see #getSegmentedLabels */ public String getLabel(String key) { return getLabel(Locales.getCurrent(), key); } /** * Returns the label of the specified key for the specified locale, or null if not found. * * @since 5.0.7 */ public String getLabel(Locale locale, String key) { Map map = (Map) _labels.get(locale); if (map == null) map = loadLabels(locale); final ExValue exVal = (ExValue) map.get(key); return exVal != null ? exVal.getValue() : null; } /** * Returns a map of segmented labels for the current locale (never null). Unlike {@link * #getLabel}, if a key of the label contains dot, it will be splitted into multiple keys and then * grouped into map. It is so-called segmented. * * <p>For example, the following property file will parsed into a couple of maps, and <code> * getSegmentedLabels()</code> returns a map containing a single entry. The entry's key is <code> * "a"</code> and the value is another map with two entries <code>"b"</code> and <code>"c"</code>. * And, the value for <code>"b"</code> is another two-entries map (containing <code>"c"</code> and * <code>"d"</code>). * * <pre><code> * a.b.c=1 * a.b.d=2 * a.e=3</pre></code> * * <p>This method is designed to make labels easier to be accessed in EL expressions. * * <p>On the other hand, {@link #getLabel} does not split them, and you could access them by, say, * <code>getLabel("a.b.d")</code>. * * @since 5.0.7 */ public Map getSegmentedLabels() { return getSegmentedLabels(Locales.getCurrent()); } /** * Returns a map of segmented labels for the specified locale (never null). Refer to {@link * #getSegmentedLabels()} for details. * * @since 5.0.7 */ public Map getSegmentedLabels(Locale locale) { final Map map = (Map) _segLabels.get(locale); if (map != null) return map; loadLabels(locale); return (Map) _segLabels.get(locale); } /** * Sets the variable resolver, which is used if an EL expression is specified. * * @since 3.0.0 */ public VariableResolver setVariableResolver(VariableResolver resolv) { final Resolver resolver = (Resolver) _xelc.getVariableResolver(); final VariableResolver old = resolver.custom; resolver.custom = resolv; return old; } /** * Registers a locator which is used to load the Locale-dependent labels from other resource, such * as servlet contexts. */ public void register(LabelLocator locator) { register0(locator); } /** * Registers a locator which is used to load the Locale-dependent labels from other resource, such * as database. * * @since 5.0.5 */ public void register(LabelLocator2 locator) { register0(locator); } private void register0(Object locator) { if (locator == null) throw new NullPointerException("locator"); synchronized (_locators) { if (!_locators.add(locator)) log.warning("Replace the old one, because it is replicated: " + locator); } reset(); // Labels might be loaded before, so... } /** * Resets all cached labels and next call to {@link #getLabel} will cause re-loading the * Locale-dependent labels. */ public void reset() { synchronized (_syncLabels) { _syncLabels.clear(); _segLabels = Collections.EMPTY_MAP; _labels = Collections.EMPTY_MAP; } } // -- private utilities --// private final Map loadLabels(Locale locale) { WaitLock lock = null; for (; ; ) { final Object o; synchronized (_syncLabels) { o = _syncLabels.get(locale); if (o == null) _syncLabels.put(locale, lock = new WaitLock()); // lock it } if (o instanceof Map) return (Map) o; if (o == null) break; // go to load the page // wait because some one is creating the servlet if (!((WaitLock) o).waitUntilUnlock(5 * 60 * 1000)) log.warning( "Take too long to wait loading labels: " + locale + "\nTry to load again automatically..."); } // for(;;) if (_jarcharset == null) _jarcharset = Library.getProperty("org.zkoss.util.label.classpath.charset", "UTF-8"); if (_warcharset == null) { _warcharset = Library.getProperty("org.zkoss.util.label.web.charset", null); if (_warcharset == null) _warcharset = Library.getProperty( "org.zkoss.util.label.WEB-INF.charset", "UTF-8"); // backward compatible } try { // get the class name if (locale != null) log.info("Loading labels for " + locale); Map labels = new HashMap(512); // 1. load from modules final ClassLocator locator = new ClassLocator(); for (Enumeration en = locator.getResources( locale == null ? "metainfo/i3-label.properties" : "metainfo/i3-label_" + locale + ".properties"); en.hasMoreElements(); ) { final URL url = (URL) en.nextElement(); load(labels, url, _jarcharset); } // 2. load from extra resource final List locators; synchronized (_locators) { locators = new LinkedList(_locators); } for (Iterator it = locators.iterator(); it.hasNext(); ) { Object o = it.next(); if (o instanceof LabelLocator) { final URL url = ((LabelLocator) o).locate(locale); if (url != null) load(labels, url, _warcharset); } else { final LabelLocator2 loc = (LabelLocator2) o; final InputStream is = loc.locate(locale); if (is != null) { final String cs = loc.getCharset(); load(labels, is, cs != null ? cs : _warcharset); } } } // Convert values to ExValue toExValue(labels); // merge with labels from 'super' locale if (locale != null) { final String lang = locale.getLanguage(); final String cnty = locale.getCountry(); final String var = locale.getVariant(); final Map superlabels = loadLabels( var != null && var.length() > 0 ? new Locale(lang, cnty) : cnty != null && cnty.length() > 0 ? new Locale(lang, "") : null); if (labels.isEmpty()) { labels = superlabels.isEmpty() ? Collections.EMPTY_MAP : superlabels; } else if (!superlabels.isEmpty()) { Map combined = new HashMap(superlabels); combined.putAll(labels); labels = combined; } } // add to map synchronized (_syncLabels) { _syncLabels.put(locale, labels); cloneLables(); } return labels; } catch (Throwable ex) { synchronized (_syncLabels) { _syncLabels.remove(locale); cloneLables(); } throw SystemException.Aide.wrap(ex); } finally { lock.unlock(); // unlock (always unlock to avoid deadlock) } } private void toExValue(Map labels) { if (!labels.isEmpty()) for (Iterator it = labels.entrySet().iterator(); it.hasNext(); ) { final Map.Entry me = (Map.Entry) it.next(); me.setValue(new ExValue((String) me.getValue())); } } // Copy _syncLabels to _labels. It must be called in synchronized(_syncLabels) private void cloneLables() { final Map labels = new HashMap(), segLabels = new HashMap(); for (Iterator it = _syncLabels.entrySet().iterator(); it.hasNext(); ) { final Map.Entry me = (Map.Entry) it.next(); final Object value = me.getValue(); if (value instanceof Map) { final Object key = me.getKey(); labels.put(key, value); segLabels.put(key, segment((Map) value)); } } _labels = labels; _segLabels = segLabels; } private Map segment(Map map) { for (Iterator it = map.keySet().iterator(); it.hasNext(); ) { final String key = (String) it.next(); if (key.indexOf(".") >= 0) return segmentInner(new HashMap(map)); // clone since we'll modify it } return new FilterMap(map, _fmfilter); // no special key } private FilterMap segmentInner(Map map) { final Map segFound = new HashMap(); for (Iterator it = map.entrySet().iterator(); it.hasNext(); ) { final Map.Entry me = (Map.Entry) it.next(); final String key = (String) me.getKey(); final Object val = me.getValue(); final int index = key.indexOf('.'); if (index >= 0) { it.remove(); // remove it final String newkey = key.substring(0, index); Map vals = (Map) segFound.get(newkey); if (vals == null) segFound.put(newkey, vals = new HashMap()); vals.put(key.substring(index + 1), val); } } for (Iterator it = segFound.entrySet().iterator(); it.hasNext(); ) { final Map.Entry me = (Map.Entry) it.next(); final FilterMap seged; Object o = map.put(me.getKey(), seged = segmentInner((Map) me.getValue())); if (o != null && !(o instanceof Map) /*just in case*/) { final Map m = seged.getOrigin(); o = m.put("$", o); if (o != null) m.put("$", o); // restore } } return new FilterMap(map, _fmfilter); } /** Loads all labels from the specified URL. */ private static final void load(Map labels, URL url, String charset) throws IOException { log.info("Opening " + url); // don't use MCommon since Messages might call getLabel load(labels, url.openStream(), charset); } /** Loads all labels from the specified URL. */ private static final void load(Map labels, InputStream is, String charset) throws IOException { final Map news = new HashMap(); try { Maps.load(news, is, charset); } finally { try { is.close(); } catch (Throwable ex) { } } for (Iterator it = news.entrySet().iterator(); it.hasNext(); ) { final Map.Entry me = (Map.Entry) it.next(); labels.put(me.getKey(), me.getValue()); } } private class ExValue { private Expression _expr; private String _val; public ExValue(String val) { int j; if ((j = val.indexOf("${")) >= 0 && val.indexOf('}', j + 2) >= 0) { try { _expr = _expf.parseExpression(_xelc, val, String.class); return; } catch (Throwable ex) { log.error("Illegal expression: " + val, ex); } } _expr = null; _val = val; } public String getValue() { return _expr != null ? (String) _expr.evaluate(_xelc) : _val; } } private class Resolver implements VariableResolver, java.io.Serializable { private VariableResolver custom; public Object resolveVariable(String name) { if (custom != null) { final Object o = custom instanceof VariableResolverX ? ((VariableResolverX) custom).resolveVariable(null, null, name) : custom.resolveVariable(name); if (o != null) return o; } return getSegmentedLabels().get(name); } } }
/** * Listener to init and cleanup the hibernate session automatically, implement the Hibernate's "Open * Session In View" pattern without JTA support. This listener is used with {@link HibernateUtil}, * or it will not work. * * <p>In WEB-INF/zk.xml, add following lines: * * <pre><code> * <listener> * <description>Hibernate "OpenSessionInView" Listener</description> * <listener-class>org.zkoss.zkplus.hibernate.OpenSessionInViewListener</listener-class> * </listener> * </code></pre> * * <p>Applicable to Hibernate version 3.2.ga or later * * @author henrichen */ public class OpenSessionInViewListener implements ExecutionInit, ExecutionCleanup { private static final Log log = Log.lookup(OpenSessionInViewListener.class); // -- ExecutionInit --// public void init(Execution exec, Execution parent) { if (parent == null) { // the root execution of a servlet request log.debug("Starting a database transaction: " + exec); HibernateUtil.currentSession().beginTransaction(); } } // -- ExecutionCleanup --// public void cleanup(Execution exec, Execution parent, List errs) { if (parent == null) { // the root execution of a servlet request try { if (errs == null || errs.isEmpty()) { // Commit and cleanup log.debug("Committing the database transaction: " + exec); HibernateUtil.currentSession().getTransaction().commit(); } else { final Throwable ex = (Throwable) errs.get(0); if (ex instanceof StaleObjectStateException) { // default implementation does not do any optimistic concurrency // control; it simply rollback the transaction. handleStaleObjectStateException(exec, (StaleObjectStateException) ex); } else { // default implementation log the stacktrace and then rollback // the transaction. handleOtherException(exec, ex); } } } finally { HibernateUtil.closeSession(); // always close it } } } /** * Default StaleObjectStateException handler. This implementation does not implement optimistic * concurrency control! It simply rollback the transaction. * * <p>Application developer might want to extends this class and override this method to do other * things like compensate for any permanent changes during the conversation, and finally restart * business conversation. Or maybe give the user of the application a chance to merge some of his * work with fresh data... what can be done here depends on the applications design. * * @param exec the exection to clean up. * @param ex the StaleObjectStateException being thrown (and not handled) during the execution */ protected void handleStaleObjectStateException(Execution exec, StaleObjectStateException ex) { log.error("This listener does not implement optimistic concurrency control!"); rollback(exec, ex); } /** * Default other exception (other than StaleObjectStateException) handler. This implementation * simply rollback the transaction. * * <p>Application developer might want to extends this class and override this method to do other * things like compensate for any permanent changes during the conversation, and finally restart * business conversation... what can be done here depends on the applications design. * * @param exec the exection to clean up. * @param ex the Throwable other than StaleObjectStateException being thrown (and not handled) * during the execution */ protected void handleOtherException(Execution exec, Throwable ex) { // Rollback only ex.printStackTrace(); rollback(exec, ex); } /** * rollback the current session. * * @param exec the exection to clean up. * @param ex the StaleObjectStateException being thrown (and not handled) during the execution */ private void rollback(Execution exec, Throwable ex) { try { if (HibernateUtil.currentSession().getTransaction().isActive()) { log.debug("Trying to rollback database transaction after exception:" + ex); HibernateUtil.currentSession().getTransaction().rollback(); } } catch (Throwable rbEx) { log.error("Could not rollback transaction after exception! Original Exception:\n" + ex, rbEx); } } }
/** * Utilities to handle java.io.Serializable. * * @author tomyeh */ public class Serializables { /** * The logger called org.zkoss.io.serializable used to log serialization information. You could * set it to DEBUG, such that the not-serializable and ignored values will be logged. * * @since 5.0.7 */ public static final Log logio = Log.lookup("org.zkoss.io.serializable"); private Serializables() {} /** * Writes only serializable entries of the specified map. Non-serializable attributes are ignored. */ public static <K, V> void smartWrite(ObjectOutputStream s, Map<K, V> map) throws IOException { if (map != null) { final boolean debug = logio.debugable(); for (Map.Entry<K, V> me : map.entrySet()) { final K nm = me.getKey(); final V val = me.getValue(); if (((nm instanceof Serializable) || (nm instanceof Externalizable)) && (val == null || (val instanceof Serializable) || (val instanceof Externalizable))) { try { s.writeObject(nm); s.writeObject(val); } catch (java.io.NotSerializableException ex) { logio.error("Unable to serialize entry: " + nm + '=' + val); throw ex; } } else if (nm != null && debug) { logio.debug("Skip not-serializable entry: " + nm + '=' + val); } } } s.writeObject(null); // denote end-of-map } /** * Reads serializable entries back (serialized by {@link #smartWrite(ObjectOutputStream,Map)}). * * @param map the map to hold the data being read. If null and any data is read, a new map * (HashMap) is created and returned. * @return the map being read */ @SuppressWarnings("unchecked") public static <K, V> Map<K, V> smartRead(ObjectInputStream s, Map<K, V> map) throws IOException, ClassNotFoundException { for (; ; ) { final Object nm = s.readObject(); if (nm == null) break; // no more if (map == null) map = new HashMap<K, V>(); map.put((K) nm, (V) s.readObject()); } return map; } /** Writes only serializable elements of the specified collection. */ public static <T> void smartWrite(ObjectOutputStream s, Collection<T> col) throws IOException { if (col != null) { final boolean debug = logio.debugable(); for (T val : col) { if ((val instanceof Serializable) || (val instanceof Externalizable)) { try { s.writeObject(val); } catch (java.io.NotSerializableException ex) { logio.error("Unable to serialize item: " + val); throw ex; } } else if (val != null && debug) { logio.debug("Skip not-serializable item: " + val); } } } s.writeObject(null); } /** * Reads serializable elements back (serialized by {@link * #smartWrite(ObjectOutputStream,Collection)}) * * @param col the collection to hold the data beinig read. If null and and data is read, a new * collection (LinkedList) is created and returned. * @return the collection being read */ @SuppressWarnings("unchecked") public static <T> Collection<T> smartRead(ObjectInputStream s, Collection<T> col) throws IOException, ClassNotFoundException { for (; ; ) { final Object val = s.readObject(); if (val == null) break; // no more if (col == null) col = new LinkedList<T>(); col.add((T) val); } return col; } /** * Reads serializable elements back (serialized by {@link * #smartWrite(ObjectOutputStream,Collection)}) * * @param col the collection to hold the data beinig read. If null and and data is read, a new * collection (LinkedList) is created and returned. * @return the collection being read * @since 6.0.0 */ @SuppressWarnings("unchecked") public static <T> List<T> smartRead(ObjectInputStream s, List<T> col) throws IOException, ClassNotFoundException { for (; ; ) { final Object val = s.readObject(); if (val == null) break; // no more if (col == null) col = new LinkedList<T>(); col.add((T) val); } return col; } /** * Writes only serializable elements of the specified array. * * <p>To read back, use {@link #smartRead(ObjectInputStream, Collection)}. * * @since 3.0.0 */ public static <T> void smartWrite(ObjectOutputStream s, T[] ary) throws IOException { if (ary != null) { final boolean debug = logio.debugable(); for (int j = 0; j < ary.length; ++j) { final T val = ary[j]; if ((val instanceof Serializable) || (val instanceof Externalizable)) { try { s.writeObject(val); } catch (java.io.NotSerializableException ex) { logio.error("Unable to serialize item: " + val); throw ex; } } else if (val != null && debug) { logio.debug("Skip not-serializable item: " + val); } } } s.writeObject(null); } /** * Writes the given value only if it is serializable. If not, null is written. * * @since 5.0.7 */ public static void smartWrite(ObjectOutputStream s, Object val) throws IOException { final boolean bser = val instanceof java.io.Serializable || val instanceof java.io.Externalizable; s.writeObject(bser ? val : null); if (!bser && val != null && logio.debugable()) logio.debug("Skip not-serializable object: " + val); } }