All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sunlabs.brazil.template.QueueTemplate Maven / Gradle / Ivy

The newest version!
/*
 * QueueTemplate.java
 *
 * Brazil project web application toolkit,
 * export version: 2.3 
 * Copyright (c) 2006 Sun Microsystems, Inc.
 *
 * Sun Public License Notice
 *
 * The contents of this file are subject to the Sun Public License Version 
 * 1.0 (the "License"). You may not use this file except in compliance with 
 * the License. A copy of the License is included as the file "license.terms",
 * and also available at http://www.sun.com/
 * 
 * The Original Code is from:
 *    Brazil project web application toolkit release 2.3.
 * The Initial Developer of the Original Code is: suhler.
 * Portions created by suhler are Copyright (C) Sun Microsystems, Inc.
 * All Rights Reserved.
 * 
 * Contributor(s): suhler.
 *
 * Version:  1.7
 * Created by suhler on 06/01/27
 * Last modified by suhler on 06/12/10 10:12:38
 *
 * Version Histories:
 *
 * 1.7 06/12/10-10:12:38 (suhler)
 *   document "glob" attribute, add "delim"
 *   .
 *   .
 *
 * 1.6 06/11/13-15:40:28 (suhler)
 *   - internal shuffling in preparation for queue aging
 *
 * 1.5 06/08/04-09:35:59 (suhler)
 *   add an option to getQ to not create the Q if it doesn't already exist
 *
 * 1.4 06/06/15-09:54:15 (suhler)
 *   doc fixes
 *
 * 1.3 06/06/14-15:45:58 (suhler)
 *   add enough information to a queue so applications may destroy it when it
 *   is no longer in use.  This is intended to help the AsteriskTemplate
 *   decide when a client has gone away without unregistering for events.
 *   This allows long runnig applications that use Queue to dispose of unused
 *   ones in an application specific way
 *
 * 1.2 06/01/27-08:36:01 (suhler)
 *   Version from the Asterisk package
 *
 * 1.2 06/01/27-08:27:18 (Codemgr)
 *   SunPro Code Manager data about conflicts, renames, etc...
 *   Name history : 1 0 handlers/templates/QueueTemplate.java
 *
 * 1.1 06/01/27-08:27:17 (suhler)
 *   date and time created 06/01/27 08:27:17 by suhler
 *
 */

package sunlabs.brazil.template;

import java.util.Vector;
import java.util.StringTokenizer;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import sunlabs.brazil.session.SessionManager;
import sunlabs.brazil.util.Glob;
import sunlabs.brazil.server.Server;
import sunlabs.brazil.util.StringMap;
import sunlabs.brazil.template.RewriteContext;
import sunlabs.brazil.template.Template;

/* 
 * New version of Q template.
 * Instead of "message" and "meta", we will xfer a "properties" table
 * if name/value pairs.  This should be backward compatible
 */

/**
 * Template class for Managing simple Queues, allowing text communication
 * among sessions.
 * The "name" attribute names the Q.  When enqueueing messages, "name"
 * is a white space separated list of queue recipients. When Dequeueing
 * Messages, "name" is the recipient Queue.
 * 

* The following tags are recognized. *

    *
  • <enqueue name="recipients ..." data=var1 var2 ... varn" * [glob="..." delim="." from="sender" nocreate="true|false"] > *

    *

  • <dequeue name="name" prepend="props prefix" * timelimit="sec"> * If "timelimit" has a suffix of "ms", then the time is taken in ms. *
  • <queueinfo name="q_name" prepend="props prefix" clear remove=true|false create=true|false> *
* This format is supported for backward compatibility with the old behavior. * (Just in case the compatibility isn't quite right, see QueueTemplateOld). *
    *
  • <enqueue name="recipients ..." meta="anything" message="msg" * from="sender" [nocreate="true|false"] > *
* * @author Stephen Uhler * @version @(#)QueueTemplate.java 1.5 1.5 */ public class QueueTemplate extends Template { public static final String Q_ID = "q"; // session table key /* * We need to remove stale queues. For now, keep track of all * Q's created so we can at least find them. * XXX this should be per-server, not static. */ // static Hashtable queueTable = new Hashtable(); /** * Add a text message onto a named queue. * Attributes:
* - name: the name of the queue (required). "name" is a delim * list of queues to send the data too.
* - from: the sender name
* - data: a set of property names to send as name/value pairs. * If Names provided of the form nnn#vvv, then "vvv" is taken as the * value to use if it is not already set, otherwise the empty * string is used as the value.
* - nocreate: If set, The item will not be Queued if the Q for that * recipient does not already exist.
* - force: put to the queue even if its closed
* - delim: The delimiter character for the list of names. (It defaults * to " " for backward compatibility with the previous behavior) *

* The property "count" contains the number of recipients for which * the message was successfully Queued. The property "error.name" * will contain an error message if Queueing failed. In both cases, * the template prefix will be prepended. It is not considered an * error condition for a message not to be delivered to a non existent * Queue if "nocreate" is set. *

* The experimental attribute "glob" may be used instead of "name", * in which case name is taken as the list of "delim" separated * tokens named by the first wildcard substring of the matching * glob pattern. */ public void tag_enqueue(RewriteContext hr) { String names = hr.get("name"); String glob = hr.get("glob"); // specify names as a glob pattern in properties String from = hr.get("from", "anonymous"); String delim = hr.get("delim", " "); boolean noCreate = hr.isTrue("nocreate"); boolean force = hr.isTrue("force"); String vars=hr.get("data"); // these are obsolete, and have been replaced by "data" String meta = hr.get("meta"); String message = hr.get("message", "Hello"); hr.killToken(); debug(hr); /* supply names as a glob pattern - experimental */ if (names==null && glob != null) { StringBuffer sb = new StringBuffer(); Enumeration e = hr.request.props.propertyNames(); String[] substr = new String[1]; boolean ok = false; while(e.hasMoreElements()) { if (Glob.match(glob, (String) e.nextElement(), substr)) { sb.append(ok?delim:"").append(substr[0]); ok=true; } } if (ok) { names = sb.toString(); hr.request.log(Server.LOG_DIAGNOSTIC, hr.prefix, "enqueue " + glob + "=(" + names + ")"); } } if (names==null) { debug(hr, "Missing Q name"); return; } // gather dictionary of values to put into Q. We'll use a // StringMap as we don't expect that many items. We could // do wildcard matching. Missing values get "". StringMap data = new StringMap(); if (vars != null) { StringTokenizer st = new StringTokenizer(vars); while (st.hasMoreTokens()) { String name = st.nextToken(); String dflt = ""; int idx = name.indexOf("#"); if (idx > 0) { dflt = name.substring(idx+1); name=name.substring(0,idx); } data.put(name, hr.request.props.getProperty(name, dflt)); } } else { // backward compatible data.put("message", message); if (meta != null) { data.put("meta", meta); } } QueueItem item = new QueueItem(from, data); // set to the queue or queues StringTokenizer st = new StringTokenizer(names, delim); int count = 0; while (st.hasMoreTokens()) { String name = st.nextToken(); Queue q = getQ(name, !noCreate); if (q != null && !q.put(item, force)) { hr.request.props.put(hr.prefix + "error." + name, " Q full"); } else { count++; } } hr.request.props.put(hr.prefix + "count", "" + count); } /** * Allow a message to be enqueued from java code. * Use the {@link #enqueue(String to, String from, Dictionary data, * boolean noCreate, boolean force)} method instead. * * @deprecated * @param to: The queue name * @param from: The sender of the data * @param message: The message to enqueue * @param meta: The meta data, if any * @param noCreate: If true, don't create a Q if it doesn't already exist * @param force: Force item onto Q even if it is closed * @return True, if the data was enqueued */ public static boolean enqueue(String to, String from, String message, String meta, boolean noCreate, boolean force) { StringMap data = new StringMap(); data.put("message", message); data.put("meta", meta); QueueItem item = new QueueItem(from, data); return enqueue(to, from, data, noCreate, force); } /** * Allow a message to be enqueued from java code. * @param to: The queue name (only a single q) * @param from: The sender of the data * @param data: a dictionary f name/value pairs * @param noCreate: If true, don't create a Q if it doesn'a already exist * @param force: Force item onto Q even if it is closed * @return True, if the data was enqueued */ public static boolean enqueue(String to, String from, Dictionary data, boolean noCreate, boolean force) { QueueItem item = new QueueItem(from, data); Queue q = getQ(to, !noCreate); // for backward compatibility item.message = (String) data.get("message"); item.meta = (String) data.get("meta"); return (q != null && q.put(item, force)); } /** * Remove a Queue, freeing its resources. */ public static void destroyQueue(String name) { SessionManager.remove(name, Q_ID); // queueTable.remove(name); } /** * Remove an item from the queue, and generate the appropriate properties. * Attributes:
*

*
name
The name of the queue to examine *
prepend
The prefix in the properties table to use to * set the results. Defaults to our prefix. *
timelimit
how long to wait for an item to appear, in sec. * Defaults to 30. *
* If an item is retrieved, the following request properties * are set (preceded by prepend): *
*
age
Set how long the message has been q's (in seconds) *
sent
Set the timestamp (in sec) of when the item was q'd. *
items
Set the number of Q'd items. *
from
Set the (unauthenticated) sender. *
error
Something went wrong. Set an error message. *
* In addition, all name/value pairs send as part of the message * will be set as well. (In prewvious versions of this template, * that consists of "message" and (optionally) "meta".) *

* Note: this tag blocks until an item is received in the Queue * (or a timelimit expires). As template processing is synchronized * based on sessions, care should be taken to avoid blocking * other (unrelated) session based requests while waiting on * the queue. */ public void tag_dequeue(RewriteContext hr) { String name = hr.get("name"); String prepend = hr.get("prepend", hr.prefix); String timelimit = hr.get("timelimit", "30"); if (!prepend.endsWith(".")) { prepend += "."; } int timer = 30; int scale=1000; if (timelimit.endsWith("ms")) { timelimit = timelimit.substring(0,timelimit.length()-2); scale=1; } try { timer = Integer.decode(timelimit).intValue(); } catch (Exception e) {} hr.killToken(); debug(hr); if (name == null) { debug(hr, "Missing Q name"); return; } Queue q = getQ(name); hr.request.log(Server.LOG_DIAGNOSTIC, hr.prefix, name + ": Q.get(" + timer + ")..."); QueueItem item = (QueueItem) q.get(timer*scale); if (item != null) { hr.request.log(Server.LOG_DIAGNOSTIC, hr.prefix, name + ": Q.get(" + timer + ") " + item); // dump message into properties Enumeration keys = item.data.keys(); while(keys.hasMoreElements()) { String key = (String) keys.nextElement(); hr.request.props.put(prepend + key, item.data.get(key)); } // now put meta-data into properties, overriding message // data if needed (?) hr.request.props.put(prepend + "from", item.from); hr.request.props.put(prepend + "age", "" + (System.currentTimeMillis() - item.timestamp)/1000); hr.request.props.put(prepend + "sent", "" + (item.timestamp/1000)); } else { hr.request.log(Server.LOG_DIAGNOSTIC, hr.prefix, name + ": Q.get() Timeout"); hr.request.props.put(prepend + "error", "timelimit"); } return; } /** * Program access to the Q. * @param name; The name of the Q. A new Q will be created if * it doesn't already exist. * @param timelimit: how long (in ms) to wait before returning * @return The Queue item, or Null if no item is available. */ public static QueueItem dequeue(String name, int timelimit) { Queue q = getQ(name); return (QueueItem) q.get(timelimit*1000); } /** * Return info about the Q, and optionally clear or remove it. * If the queue doesn't already exist, it is created. * Attributes:
*

*
name
The name of the queue to examine *
prepend
The prefix in the properties table to use to * set the results. Defaults to our prefix. *
clear
If set, then clear the queue *
remove
If set, then remove the queue *
closed=[true|false]
set the closed state of the Q *
create=[true|false]
Should the Q be created if it doesn't already * exist (defaults to true). *
* The following request properties * are set (preceded by prepend): *
*
lastIn
The timestamp (ms) of the last Q insert attempt. *
lastOut
The timestamp (ms) of the last Q retrieval attempt. *
size
The number of items in the Q. *
count
The total number of items inserted into the Q *
created
The timestamp (in ms) of the Q's creation *
expires
The Q's expiration period (in ms) *
* If the Q doesn't exist (e.g. create=false), then the property * "error" is set. */ public void tag_queueinfo(RewriteContext hr) { String name = hr.get("name"); String prepend = hr.get("prepend", hr.prefix); boolean clear = hr.isTrue("clear"); boolean remove = hr.isTrue("remove"); boolean notify = hr.isTrue("notify"); boolean closed = (hr.get("closed") != null); boolean shouldCreate = (hr.get("create")==null || hr.isTrue("create")); int expire = -1; try { expire = Integer.decode(hr.get("expires", "-1")).intValue(); } catch (Exception e) {} if (!prepend.endsWith(".")) { prepend += "."; } hr.killToken(); debug(hr); if (name == null) { debug(hr, "Missing Q name"); return; } Queue q = getQ(name, shouldCreate); if (q == null) { hr.request.props.put(prepend + "error", "Q doesn't exist"); return; } hr.request.props.put(prepend + "lastIn", "" + q.lastIn()); hr.request.props.put(prepend + "lastOut", "" + q.lastOut()); hr.request.props.put(prepend + "size", "" + q.size()); hr.request.props.put(prepend + "count", "" + q.count()); hr.request.props.put(prepend + "closed", "" + q.isClosed()); hr.request.props.put(prepend + "created", "" + q.getCreated()); hr.request.props.put(prepend + "expires", "" + q.getExpires()); if (closed) { q.setClosed(hr.isTrue("closed")); } if (clear) { q.clear(); } if (notify) { q.kick(); } if (expire != -1) { q.setExpires(expire); } if (remove) { destroyQueue(name); } } /** * Return a Q. * All calls to the session manager go through here, so we * can reap old queues some day. */ public static Queue getQ(String name, boolean create) { Queue q = (Queue) SessionManager.getSession(name, Q_ID, create ? Queue.class : null); /* if (q != null) { queueTable.put(name, q); } */ return q; } public static Queue getQ(String name) { return getQ(name, true); } /** * Return a hashtable of all the Q's by name. public static Hashtable getAllQueues() { return queueTable; } */ /** * A bag of items to keep on the Q. * We could add other stuff later. */ public static class QueueItem { public long timestamp; // when the item was queued public String from; // Who sent it public Dictionary data; // message name/value pairs // these are for backward compatibility only. Don't use. // They are needed by the old dequeue() method. public String message; // What is the message public String meta; // Arbitrary meta info about the message public QueueItem(String from, Dictionary data) { timestamp = System.currentTimeMillis(); this.from=from; this.data = data; // backward compatibility message=(String) data.get("message"); meta=(String) data.get("meta"); } /** * Add an item to the Q. * "meta" is application specific "meta" data associated with * this Q item. * @deprecated * Use the other constructor instead */ public QueueItem(String from, String message, String meta) { StringMap data = new StringMap(); data.put("message", message); data.put("meta", meta); timestamp = System.currentTimeMillis(); this.from=from; // backward compatibility this.message = message; this.meta = meta; } public String toString() { return from + ": " + data + " (" + timestamp + ")"; } } /** * Create an object queue. "Getters" wait 'till something * appears in the queue. */ public static class Queue { public static int max=100; Vector q; // where to hold the items Object mutex = new Object(); long lastGet; // timestamp of last get long lastPut; // timestamp of last put int maxItems; // max q size int count; // number of items Q'd boolean closed; // The queue is closed long created; // when was this queue created (ms) long expires; // how much idle time before expiration /** * Create a new Q of a maximum possible size */ public Queue() { lastGet = lastPut = -1; count = 0; maxItems = max; q = new Vector(); closed = false; created = System.currentTimeMillis(); expires = 0; // doesn't expire } /** * Return the next item on the queue, waiting for up to * "timeout" seconds or for an interrupt. * @return the top of the Q, or null. */ public Object get(int timeout) { try { String name = Thread.currentThread().getName(); synchronized (mutex) { if (q.size() == 0) { mutex.wait(timeout==0 ? 1 : timeout); } lastGet = System.currentTimeMillis(); if (q.size() > 0) { Object data = q.firstElement(); q.removeElementAt(0); return data; } } } catch (InterruptedException e) {} return null; } /** * Put an item on the queue if it's open and not full. */ public boolean put(Object item) { return put(item, false); } /** * Put an item on the queue if it's not full. * If "force" is true, override the "closed" flag. */ public boolean put(Object item, boolean force) { if (closed && !force) { return false; } boolean result; String name = Thread.currentThread().getName(); synchronized (mutex) { lastPut = System.currentTimeMillis(); if (q.size() < maxItems) { count++; q.addElement(item); mutex.notifyAll(); result = true; } else { result = false; } } return result; } /** * How many items are queue'd. */ public int size() { return q.size(); } /** * Send a notify: for debugging */ public void kick() { synchronized (mutex) { // System.out.println("*NOTIFY* " + mutex); mutex.notifyAll(); } } /** * Return the last time a Q insertion was attempted. * @return -1 if no attempts were made. */ public long lastIn() { return lastPut; } /** * Return the last time a Q removal was attempted. * @return -1 if no attempts were made. */ public long lastOut() { return lastGet; } /** * Return the total number of items Q'd. * @return The # of Q'd items. */ public long count() { return count; } /** * Get the expiration period of the Queue (in ms). * The notion of when a queue expires is application dependent. * Applications can look at count() lastIn(), lastOut(). and * created() to determine when the Q is expired for them. */ public long getExpires() { return expires; } /** * Set the expiration period of the Queue (in ms). */ public void setExpires(long expires) { this.expires = expires; } /** * Return creation time (ms since epoch). */ public long getCreated() { return created; } /** * Clear the queue. */ public void clear() { q.removeAllElements(); // lastGet = lastPut = -1; // count = 0; } /** * Set the closed state. */ public boolean setClosed(boolean closed) { boolean result = this.closed; this.closed = closed; return result; } /** * Get the closed state. */ public boolean isClosed() { return this.closed; } public String toString() { return("Queue got=" + lastGet + " put=" + lastPut + " " + q); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy