/* * #%L * carewebframework * %% * Copyright (C) 2008 - 2016 Regenstrief Institute, Inc. * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * This Source Code Form is also subject to the terms of the Health-Related * Additional Disclaimer of Warranty and Limitation of Liability available at * * http://www.carewebframework.org/licensing/disclaimer. * * #L% */ package org.carewebframework.ui.event; import java.util.HashMap; import java.util.Map; import org.apache.commons.beanutils.MethodUtils; import org.zkoss.zk.au.AuRequest; import org.zkoss.zk.au.AuService; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.Desktop; import org.zkoss.zk.ui.event.EventListener; import org.zkoss.zk.ui.event.EventQueue; import org.zkoss.zk.ui.event.EventQueues; import org.zkoss.zk.ui.util.DesktopCleanup; /** * This class implements a queue that allows one desktop to execute methods on a target on another * desktop (the owner of the queue) via invocation requests. */ public class InvocationRequestQueue implements EventListener<InvocationRequest> { /** * Listens to desktop async requests. */ private class DesktopListener implements AuService { /** * Resets the keep alive timer. * * @see org.zkoss.zk.au.AuService#service(org.zkoss.zk.au.AuRequest, boolean) */ @Override public boolean service(AuRequest request, boolean everError) { resetKeepAlive(); return false; } } private static final Map<String, InvocationRequestQueue> messageQueues = new HashMap<>(); private static final int TIMEOUT_INTERVAL = 10000; // Timeout interval in milliseconds private final Object target; private final EventQueue<InvocationRequest> eventQueue; private final Desktop desktop; private final String queueName; private final DesktopListener desktopListener = new DesktopListener(); private final InvocationRequest onClose; private boolean closed; private long lastKeepAlive; /** * Looks up and returns the named queue registered with the specified owner. * * @param ownerId This is the unique desktop id of the queue's owner. * @param queueName The queue name. * @return The requested message queue, or null if not found. */ public static InvocationRequestQueue getQueue(String ownerId, String queueName) { synchronized (messageQueues) { return messageQueues.get(getQualifiedQueueName(ownerId, queueName)); } } /** * Returns the queue name qualified by the owner id. * * @param ownerId This is the unique desktop id of the queue's owner. * @param queueName The queue name. * @return The qualified queue name. */ public static String getQualifiedQueueName(String ownerId, String queueName) { return ownerId + "_" + queueName; } /** * Create an invocation request. * * @param methodName Name of method to invoke on the target. * @param args Arguments to pass to the invoked method. * @return The newly created invocation request. */ public static InvocationRequest createRequest(String methodName, Object... args) { return new InvocationRequest(methodName, args); } /** * Create an invocation request queue for the specified target. * * @param target Target of invocation requests sent to the queue. * @param queueName The name of the queue to be created. * @param onClose Invocation request to send to the target upon queue closure (may be null). */ public InvocationRequestQueue(Component target, String queueName, InvocationRequest onClose) { this(target.getDesktop(), target, queueName, onClose); } /** * Create a help message queue for the specified desktop and target. * * @param desktop Desktop instance that owns the queue. * @param target Target of requests sent to the queue. * @param queueName The name of the queue to be created. * @param onClose Invocation request to send to the target upon queue closure (may be null). */ public InvocationRequestQueue(Desktop desktop, Object target, String queueName, InvocationRequest onClose) { super(); this.target = target; this.desktop = desktop; this.queueName = getQualifiedQueueName(desktop.getId(), queueName); this.onClose = onClose; resetKeepAlive(); eventQueue = createQueue(); desktop.addListener(new DesktopCleanup() { /** * Closes the invocation request queue when the owning desktop is destroyed. * * @see org.zkoss.zk.ui.util.DesktopCleanup#cleanup(org.zkoss.zk.ui.Desktop) */ @Override public void cleanup(Desktop desktop) throws Exception { close(); } }); desktop.addListener(desktopListener); } /** * Creates a queue for the specified owner and establishes a subscriber. The queue will be * automatically removed when the owning desktop is destroyed. * * @return The newly created event queue. */ private EventQueue<InvocationRequest> createQueue() { EventQueue<InvocationRequest> eventQueue = lookupQueue(true); eventQueue.subscribe(this); registerQueue(); return eventQueue; } /** * Registers a message queue. A run time exception is thrown if a queue by the same name is * already registered for this desktop. */ private void registerQueue() { synchronized (messageQueues) { if (messageQueues.get(queueName) == null) { messageQueues.put(queueName, this); } else { throw new RuntimeException("An invocation request queue '" + queueName + "' already exists for this desktop."); } } } /** * Unregisters the message queue. If the queue is no longer registered, the request is ignored. */ private void unregisterQueue() { synchronized (messageQueues) { if (messageQueues.get(queueName) == this) { messageQueues.remove(queueName); } } } /** * Close the message queue. */ protected void close() { if (!closed) { closed = true; desktop.removeListener(desktopListener); unregisterQueue(); EventQueues.remove(queueName, desktop.getWebApp()); if (onClose != null) { onEvent(onClose); } } } /** * Returns the qualified name of the associated event queue. * * @return The qualified queue name. */ public String getQualifiedQueueName() { return queueName; } /** * Returns a reference to the associated queue. * * @param autoCreate If true and the queue does not yet exist, it is created. * @return The associated event queue. May return null if autoCreate is false and the queue no * longer exists. */ private EventQueue<InvocationRequest> lookupQueue(boolean autoCreate) { return EventQueues.lookup(queueName, EventQueues.APPLICATION, autoCreate); } /** * Queue a request. * * @param methodName Name of method to invoke on the target. * @param args Arguments to pass to the invoked method. */ public void sendRequest(String methodName, Object... args) { sendRequest(createRequest(methodName, args)); } /** * Queue a request. * * @param request The event packaging the request. */ public void sendRequest(InvocationRequest request) { eventQueue.publish(request); } /** * Returns true if this queue is alive. * * @return True if this queue is alive. */ public boolean isAlive() { checkKeepAlive(); return !closed && desktop.isAlive() && lookupQueue(false) != null; } /** * Resets the keep-alive timer. */ private void resetKeepAlive() { lastKeepAlive = System.currentTimeMillis(); } /** * If keep-alive is enabled, check to see if it has exceeded the threshold. If it has, it is * assumed that the owner desktop is no longer valid and closes the queue. */ private void checkKeepAlive() { if (!closed && System.currentTimeMillis() - lastKeepAlive > TIMEOUT_INTERVAL) { close(); } } /** * Invokes the method on the target as specified by the event. * * @param request The invocation request. */ @Override public void onEvent(InvocationRequest request) { try { MethodUtils.invokeMethod(target, request.getName(), request.getArgs()); } catch (Throwable e) {} } }