/*
* IMTemplate.java
*
* Brazil project web application toolkit,
* export version: 2.3
* Copyright (c) 2008-2009 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.2
* Created by suhler on 08/08/18
* Last modified by suhler on 09/07/07 13:57:22
*
* Version Histories:
*
* 1.2 09/07/07-13:57:22 (suhler)
* added vcard, better documentation, debug flags
*
* 1.2 70/01/01-00:00:02 (Codemgr)
* SunPro Code Manager data about conflicts, renames, etc...
* Name history : 1 0 xmpp/IMTemplate.java
*
* 1.1 08/08/18-14:55:58 (suhler)
* date and time created 08/08/18 14:55:58 by suhler
*
*/
package sunlabs.brazil.xmpp;
import sunlabs.brazil.server.FileHandler;
import sunlabs.brazil.server.Server;
import sunlabs.brazil.template.RewriteContext;
import sunlabs.brazil.template.Template;
import sunlabs.brazil.util.regexp.Regexp;
import sunlabs.brazil.util.StringMap;
import sunlabs.brazil.template.QueueTemplate.Queue;
import sunlabs.brazil.template.QueueTemplate;
import java.io.IOException;
import java.util.Properties;
import java.util.Hashtable;
import java.util.Dictionary;
import java.util.Iterator;
import java.util.StringTokenizer;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.MessageTypeFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.RosterGroup;
import org.jivesoftware.smack.packet.RosterPacket;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.packet.VCard;
/**
* XMPP client template. (Incomplete - still under construction.)
* This uses the SMACK library at:
* {@link http://www.igniterealtime.org/projects/smack/}
* It has been tested with version 3.0.4.
*/
public class IMTemplate extends Template {
boolean debug=false;
static Hashtable connections = new Hashtable();
static public final String[] vcardFields = new String[] {
"FN", "NICKNAME", "PHOTO", "BDAY", "JABBERID", "MAILER", "TZ",
"GEO", "TITLE", "ROLE", "LOGO", "NOTE", "PRODID", "REV",
"SORT-STRING", "SOUND", "UID", "URL", "DESC"
};
/**
*
* <im command=login id=xx server=xx
* user=xx pass=xx
* [host=xx port=nn prepend=xxx]
* [subscribe=reject|decide]
* [qid=xx]
* >
* - Log into an XMPP server. "host" and "port" are only required
* when the values implied by the "server" attribute are not correct.
* "id" is an arbirary identifier used to identify this session. It
* must be used with all other "im" commands, and is also the name of
* the Queue used to receive messages from.
* If "subscribe" is specified, it indicates the buddy subscription
* mode, which defaults to automatic.
* If "decide" is used, then it is up to the client to accept or
* reject subscriptions. (The default is to accept all subscriptions).
*
* The value of "qid" can be used to override the Q name, allowing
* multiple accounts (id) to be used with the same Q (qid).
*
<im command=logout id=xx>
* - Log out of this session. Any entries in the Queue remain.
*
<im command=message id=xx to=xxx body=xxx
* [thread=xxx]>
* - Send a message to another user (e.g. jow.blow@gmail.com).
*
<im command=presence id=xx
* [mode=available|away|chat|dnd|xa
* type=available|unavailable|subscribe|subscribed|unsubscribe|unsubscribed
* body=xxx to=xxx]>
* - Send presence information.
*
* - mode
- If type is "available", meaning the client is accepting
* messages, the mode specifies the client's presence status.
* (defaults to "available")
*
- type
- This sets the message type. "available", and "unavailable"
* are used to indicate the client state, which is "available" if the client
* is connected (even if the "mode" is unavailable), and "unavailable" if
* they are disconnected. "available" is the default.
*
* The other types are for managing subscriptions:
* - subscribe: request a subscription ("to" is required)
* - subscribed: grant a subscription request
* - unsubscribe: request subscription removal ("to" is required)
* - unsubscribed: grant a subscription removal request, or reject a subscription
* request.
*
- body
- The presence message (for type=available)
*
* <im command=getroster id=xx [prepend=xx]>
* - Retrieve your buddy list (roster in smack-speak).
*
<im command=addroster id=xx user=xx
* [name=xx groups="g1 g2..."]>
* - Add a buddy to the buddy list, in the specified groups. Groups are
* created as-needed.
*
<im command=deluser id=xx user=xx>
* - Remove a buddy from the buddy list.
*
<im command=vcard id=xx [user=xx prepend=xx]>
* - Retrieve VCARD information. If "user" isn' specified, then the
* currently logged-in user's vCard is returned.
*
- <dequeue name=[qid] .....>
*
- This is used to retrieve messages from the XMPP system, using the
* {@link sunlabs.brazil.template.QueueTemplate}.
* "name" is should be the value of "qid", if specified, or "id" otherwise.
* Each dequeued item is a dictionary of name/value pairs describing
* the event.
*
* The following entries are always present:
*
* - host: The host this connection is connected to
*
- conID: The internal connection id
*
- id: The "id" identifier for this connection
*
- user: The user connected to this host
*
- sender: The sender of this packet
*
- xml: The raw XMPP XML markup for this packet.
*
- category: presence|message|roster
*
* Other entries may be present, depending on the "category",
* including: "type", "body", "thread", and "subject" for Message
* packets, lists of "type", "status", "name", and "user" entries
* for roster packets, and lists of "body", "priority", "type", and
* "state" entries for presence packets. Inspect the set of dequeued
* properties for a complete list.
*
*
* Notes:
*
* - [prefix].status will contain the result of the command
*
- All received messages will be available from the queue "id" (or qid if
* provided) , as in:
*
*
- The "debug" attribute will cause the template to spew more stuff to
* the console.
*
- Logging in to an already logged-in "id" forces the preexisting connection
* to be dropped
*
*/
public void tag_im(RewriteContext hr) {
hr.killToken();
debug=hr.isTrue("debug");
debug(hr);
if (debug) {
System.out.println(hr.getToken());
}
String command = hr.get("command");
String id = hr.get("id");
hr.request.log(Server.LOG_DIAGNOSTIC, hr.prefix,
" id=" + id + " cmd=" + command);
if (id==null || command == null) {
debug(hr, "id or command attributes missing");
return;
}
if (command.equals("login")) {
XMPPConnection con = doLogin(hr, id);
if (con != null) {
// set the subscription state here?
Roster roster = con.getRoster();
String sub = hr.get("subscribe");
if (sub != null) {
if (sub.equals("reject")) {
roster.setSubscriptionMode(
Roster.SubscriptionMode.reject_all);
} else if (sub.equals("decide")) {
roster.setSubscriptionMode(
Roster.SubscriptionMode.manual);
}
}
mapRoster(hr, roster);
con.addPacketListener(new MessageRelay(con, id, hr.get("qid")),
new MyFilter());
}
return;
}
/**
* Every other command needs to be logged in
*/
XMPPConnection con = (XMPPConnection) connections.get(id);
if (con == null) {
logStatus(hr, "error: id (" + id + ") not logged in for: "
+ command);
return;
}
if (command.equals("logout")) {
con.sendPacket(new Presence(Presence.Type.unavailable));
con.disconnect();
connections.remove(id);
logStatus(hr, "logout succeeded");
} else if (command.equals("message")) {
String to = hr.get("to");
if (to == null) {
logStatus(hr, "error: 'to' required");
}
Message msg = new Message(to);
msg.setBody(hr.get("body", "hello"));
String thread = hr.get("thread");
if (thread != null) {
msg.setThread(thread);
}
con.sendPacket(msg);
logStatus(hr, "message sent");
} else if (command.equals("presence")) {
String mode = hr.get("mode", "available");
String type = hr.get("type");
String body = hr.get("body");
String to = hr.get("to");
Presence.Type pt = Presence.Type.available;
try {
pt = Presence.Type.valueOf(hr.get("type","available"));
} catch (IllegalArgumentException e) {}
Presence pr = new Presence(pt);
if (body != null) {
pr.setStatus(body);
}
try {
pr.setMode(Presence.Mode.valueOf(mode));
} catch (IllegalArgumentException e) {}
if (to != null) {
pr.setTo(to);
}
con.sendPacket(pr);
logStatus(hr, "presence sent");
} else if (command.equals("getroster")) { // get the current buddy list
mapRoster(hr, con.getRoster());
} else if (command.equals("addroster")) { // add a "buddy"
String user = hr.get("user");
String name = hr.get("name", user);
String groups = hr.get("groups");
if (user == null) {
logStatus(hr, "error: user required");
return;
}
Roster roster = con.getRoster();
String[] groupA = toArray(groups);
for(int i=0;groupA!=null && i" + qid + ": " + map);
}
QueueTemplate.enqueue(qid, from, map, false, false);
}
}
String getPrepend(RewriteContext hr) {
String prepend = hr.get("prepend", hr.prefix);
if (!prepend.endsWith(".")) {
prepend += ".";
}
return prepend;
}
// for testing
static class MyFilter implements PacketFilter {
public boolean accept(Packet pkt) {
// System.out.println("Accepting (" + pkt.toXML() + ")");
// I haven't figured out what packets I need yet.
return (pkt instanceof Presence) ||
(pkt instanceof Message) ||
(pkt instanceof RosterPacket);
}
}
/**
* command line interface?.
* user pass server [host port]
*/
public static void
main(String[] args) throws Exception {
}
}