View Javadoc

1   /**
2    * Copyright 2005-2006 the original author or authors.
3    *
4    * Licensed under the Gnu General Pubic License, Version 2.0 (the
5    * "License"); you may not use this file except in compliance with
6    * the License. You may obtain a copy of the License at
7    *
8    *      http://www.opensource.org/licenses/gpl-license.php
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13   * See the Gnu General Public License for more details.
14   */
15  package org.figure8.join.services.notification.im;
16  
17  import org.figure8.join.core.ParameterDefinition;
18  import org.figure8.join.core.InvalidParameterException;
19  import org.figure8.join.services.notification.SubscribersNotifier;
20  import org.figure8.join.services.notification.NotificationException;
21  import org.figure8.join.services.remoting.beans.RemoteSubscriber;
22  import org.figure8.join.util.LogUtil;
23  
24  import org.apache.commons.logging.Log;
25  import org.jivesoftware.smack.GroupChat;
26  import org.jivesoftware.smack.XMPPException;
27  import org.jivesoftware.smack.XMPPConnection;
28  import org.jivesoftware.smack.Chat;
29  import org.jivesoftware.smack.packet.Message;
30  
31  import java.util.List;
32  import java.util.Iterator;
33  import java.util.ArrayList;
34  import java.util.Properties;
35  /**
36   * This is an implementation of {@link SubscribersNotifier} using the Instant
37   * Messenger as notification media. This implementation uses the Jabber/XMPP
38   * protocol for message sending. It uses the Smack libray for that (see
39   * http://www.jivesoftware.org/smack for details)
40   * @author <a href="mailto:laurent.broudoux@free.fr">Laurent Broudoux</a>
41   * @version $Revision: 1.1 $
42   */
43  public class XMPPNotifier implements SubscribersNotifier{
44  
45     // Static -------------------------------------------------------------------
46  
47     /** Get a commons logger. */
48     private static final Log log = LogUtil.getLog(XMPPNotifier.class);
49  
50     /** Name of configurable parameter denoting the port of IM server. */
51     public static final String PORT_PARAM = "port";
52     /** Name of configurable parameter denoting the name of IM server. */
53     public static final String SERVER_PARAM = "server";
54     /** Name of configurable parameter denoting the user name for connecting to IM. */
55     public static final String USER_PARAM = "username";
56     /** Name of configurable parameter denoting the password for connecting to IM. */
57     public static final String PASSWORD_PARAM = "password";
58     /** Name of configurable parameter denoting the chat room where to send message. */
59     public static final String ROOM_PARAM = "chatRoom";
60     /** Name of configurable parameter denoting the nick name for joining chat room. */
61     public static final String PSEUDO_PARAM = "pseudo";
62  
63     /** List of {@link ParameterDefinition}s supported by this notifier */
64     protected static List parameters = new ArrayList();
65  
66     /** ParameterDefinition representation of parameter denoting the port of the IM server. */
67     protected static final ParameterDefinition portParam = new ParameterDefinition(PORT_PARAM,
68             "The port of the IM server to connect to (blanck means default)", "5222", false);
69     /** ParameterDefinition representation of parameter denoting the name of the IM server. */
70     protected static final ParameterDefinition serverParam = new ParameterDefinition(SERVER_PARAM,
71             "The name of the IM server (Service, DNS name of IP address)", "jabber.org", true);
72     /** ParameterDefinition representation of parameter denoting the user name for connecting to IM. */
73     protected static final ParameterDefinition userParam = new ParameterDefinition(USER_PARAM,
74             "The user for connecting to IM server", "jsmith@jabber.org", true);
75     /** ParameterDefinition representation of parameter denoting the password for connecting to IM. */
76     protected static final ParameterDefinition passwordParam = new ParameterDefinition(PASSWORD_PARAM,
77             "The password for connection to IM server", "mypassword", true);
78     /** ParameterDefintion representation of parameter denoting the chat room where to send messages. */
79     protected static final ParameterDefinition roomParam = new ParameterDefinition(ROOM_PARAM,
80             "The chat room or group where to send messages", "test@jabber.org", false);
81     /** ParameterDefintion representation of parameter denoting the nick name for joining chat room. */
82     protected static final ParameterDefinition pseudoParam = new ParameterDefinition(PSEUDO_PARAM,
83             "The nick name used for joining chat room or group", "jsmith", false);
84  
85     static{
86        parameters.add(portParam);
87        parameters.add(serverParam);
88        parameters.add(userParam);
89        parameters.add(passwordParam);
90        parameters.add(roomParam);
91        parameters.add(pseudoParam);
92     }
93  
94  
95     // Attributes ---------------------------------------------------------------
96  
97     /** The port of IM server to connect to */
98     private int port = -1;
99     /** The name of IM server to connect to */
100    private String server;
101    /** The username for connecting to IM server */
102    private String username;
103    /** The password for connecting to IM server */
104    private String password;
105    /** The name of chat room to send messages to (if group publishing) */
106    private String chatRoom;
107    /** The pseudo to use for joining chat room (if group publishing) */
108    private String pseudo;
109 
110 
111    // Constructors -------------------------------------------------------------
112 
113    /** Creates a new instance of XMPPNotifier */
114    public XMPPNotifier(){
115    }
116 
117    /**
118     * Creates a new instance of XMPPNotifier with mandatory attributes for connecting to IM
119     * @param server The name of IM server to connect to
120     * @param port The port of IM server to connect to
121     * @param username The username for connecting to IM server
122     * @param password The password for connecting to IM server
123     */
124    public XMPPNotifier(String server, int port, String username, String password){
125       this.server = server;
126       this.port = port;
127       this.username = username;
128       this.password = password;
129    }
130 
131 
132    // Public -------------------------------------------------------------------
133 
134    /** @return The port of IM server to connect to */
135    public int getPort(){
136       return port;
137    }
138    /** @param port The port of IM server to connect to */
139    public void setPort(int port){
140       this.port = port;
141    }
142    /** @return The name of IM server to connect to */
143    public String getServer(){
144       return server;
145    }
146    /** @param server The name of IM server to connect to */
147    public void setServer(String server){
148       this.server = server;
149    }
150    /** @return The username for connecting to IM server */
151    public String getUsername(){
152       return username;
153    }
154    /** @parma username The username for connecting to IM server */
155    public void setUsername(String username){
156       this.username = username;
157    }
158    /** @return The password for connecting to IM server */
159    public String getPassword(){
160       return password;
161    }
162    /** @param password The password for connecting to IM server */
163    public void setPassword(String password){
164       this.password = password;
165    }
166    /** @return The name of chat room to send messages to (if group publishing) */
167    public String getChatRoom(){
168       return chatRoom;
169    }
170    /** @param chatRoom The name of chat room to send messages to (if group publishing) */
171    public void setChatRoom(String chatRoom){
172       this.chatRoom = chatRoom;
173    }
174    /** @return The pseudo to use for joining chat room (if group publishing) */
175    public String getPseudo(){
176       return pseudo;
177    }
178    /** @param pseudo The pseudo to use for joining chat room (if group publishing) */
179    public void setPseudo(String pseudo){
180       this.pseudo = pseudo;
181    }
182 
183 
184    // Implementation of SubscribersNotifier ------------------------------------
185 
186    /**
187     * Extract mail addresses for subscribers to provide recipients. Use mail address
188     * for the moment : RemoteSubscriber do not have an IM identifier
189     * @param subscribers The subscribers to get recipients for
190     * @return The list of corresponding recipients.
191     */
192    public String[] extractRecipients(RemoteSubscriber[] subscribers){
193       String[] recipients = new String[subscribers.length];
194       for (int i=0; i<subscribers.length; i++){
195          recipients[i] = subscribers[i].getMailAddress();
196       }
197       return recipients;
198    }
199 
200    /**
201     * Notify subscribers of mailing list defined into Join application
202     * @param subscribers The subscriber of mailing list to notify
203     * @param title The title of notification message to send
204     * @param content The content of notification message to send
205     * @throws org.figure8.join.services.notification.NotificationException if an exception occurs during the notification operation
206     * (usually they are network related exceptions)
207     */
208    public void notify(RemoteSubscriber[] subscribers, String title, String content) throws NotificationException{
209       // Just delegate to notify() method after having extracted recipients.
210       notify(extractRecipients(subscribers), title, content);
211    }
212 
213    /**
214     * Notifier are able to notify recipients and use a title and a content within
215     * their notification messages. The definition of a recipients is voluntary abstract
216     * letting implementations or sub-interfaces defining what they consider as valid
217     * recipients (ex: mail addresses, IM identifies, etc...)
218     * @param recipients An array of recipients to notify. Recipient is described with a String
219     * @param title The title of notification message to send
220     * @param content The content of notification message to send
221     * @throws NotificationException if an exception occurs during the notification operation
222     * (usually they are network related exceptions)
223     */
224    public void notify(String[] recipients, String title, String content) throws NotificationException{
225       if (log.isDebugEnabled())
226          log.debug("Notifying recipients using " + server + " with " + username + "/" + password + " credentials");
227       // Retrieve new connection.
228       XMPPConnection con = connect();
229 
230       try{
231          // Do we use a chat room or send individual messages ?
232          if (chatRoom != null && chatRoom.length() > 0){
233             if (log.isDebugEnabled())
234                log.debug("Sending a message to the chat room " + chatRoom);
235             GroupChat chat = con.createGroupChat(chatRoom);
236             chat.join(pseudo);
237             Message msg = chat.createMessage();
238             msg.setSubject(title);
239             msg.setBody(content);
240             chat.sendMessage(msg);
241             chat.leave();
242          }
243          else{
244             // Send a message to each subscribers.
245             for (int i=0; i<recipients.length; i++){
246                if (log.isDebugEnabled())
247                   log.debug("Sending a message to recipient " + recipients[i]);
248                try{
249                   Chat chat = con.createChat(recipients[i]);
250                   Message msg = chat.createMessage();
251                   msg.setSubject(title);
252                   msg.setBody(content);
253                   chat.sendMessage(msg);
254                }
255                catch (XMPPException xe){
256                   // Do not penalize subscribers if a recipient is incorrect, just warn.
257                   log.warn("Exception while sending message to recipient " + recipients[i]);
258                   log.warn("Here's the detailed message: " + xe.getMessage());
259                }
260             }
261          }
262       }
263       catch (XMPPException xe){
264          // Alert if it's a chat room joining problem
265          if (xe.getXMPPError().getCode() == 409)
266             log.error("Exception while joining chat room. Used pseudo (" + pseudo + ") seems already used");
267 
268          log.error("Here's the detailed message: " + xe.getMessage());
269          throw new NotificationException("Exception while sending messages to XMPP server", xe);
270       }
271       finally{
272          // Do best efforts to close connection.
273          try {con.close();}
274          catch (Exception e) {/* Nothing to do here. */}
275       }
276    }
277 
278 
279    // Implementation of Configurable -------------------------------------------
280 
281    /**
282     * Get this object parameters definitions as a list
283     * @return A list of {@code ParameterDefinition} objects
284     */
285    public List getParameterDefinitionsAsList(){
286       return parameters;
287    }
288 
289    /**
290     * Get this object parameters definitions as an array
291     * @return An array of {@code ParameterDefinition} objects
292     */
293    public ParameterDefinition[] getParameterDefinitions(){
294       List result = getParameterDefinitionsAsList();
295       return (ParameterDefinition[])result.toArray(new ParameterDefinition[result.size()]);
296    }
297 
298    /**
299     * Set the value of a parameter using its nama
300     * @param parameterName The name of parameter so set value for
301     * @param parameterValue The value of the paramater
302     * @throws InvalidParameterException if this parameter is not supported by this object
303     */
304    public void setParameter(String parameterName, String parameterValue) throws InvalidParameterException{
305       if (PORT_PARAM.equals(parameterName)){
306          try {setPort(Integer.parseInt(parameterValue));}
307          catch (Exception e){
308             // Log and propagate by wrapping.
309             log.error("Port parameter is not an integer !");
310             throw new InvalidParameterException("Port parameter is not an integer");
311          }
312       }
313       else if (SERVER_PARAM.equals(parameterName))
314          setServer(parameterValue);
315       else if (USER_PARAM.equals(parameterName))
316          setUsername(parameterValue);
317       else if (PASSWORD_PARAM.equals(parameterName))
318          setPassword(parameterValue);
319       else if (ROOM_PARAM.equals(parameterName))
320          setChatRoom(parameterValue);
321       else if (PSEUDO_PARAM.equals(parameterName))
322          setPseudo(parameterValue);
323    }
324 
325    /**
326     * Set the value of a parameter using its definitions
327     * @param parameter The definitino of the paramater to set
328     * @param parameterValue The value of the parameter
329     * @throws InvalidParameterException if this parameter is not supported by this object
330     */
331    public void setParameter(ParameterDefinition parameter, String parameterValue) throws InvalidParameterException{
332       setParameter(parameter.getName(), parameterValue);
333    }
334 
335    /**
336     * Convenient methods for setting all attributes values using a single method.
337     * @param parameters Properties where keys are parameter names
338     * @throws InvalidParameterException if one of these parameters is not supported by this object
339     */
340    public void setParameters(Properties parameters) throws InvalidParameterException{
341       Iterator keys = parameters.keySet().iterator();
342       while (keys != null && keys.hasNext()){
343          String parameterName = (String)keys.next();
344          String parameterValue = parameters.getProperty(parameterName);
345          setParameter(parameterName, parameterValue);
346       }
347    }
348 
349 
350    // Protected ----------------------------------------------------------------
351 
352    /**
353     * Create a new connection and log in.
354     * @throws NotificationException if an exception occurs while creating exception.
355     * @return A new XMPPConnection with user logged in
356     */
357    protected XMPPConnection connect() throws NotificationException{
358       // Create a new connection and log in.
359       XMPPConnection con = null;
360       try{
361          con = new XMPPConnection(server, port);
362          con.login(username, password);
363          return con;
364       }
365       catch (XMPPException xe){
366          // Log and wrap exception.
367          log.error("Exception while connecting to the XMPP server " + server + ": " + xe.getMessage());
368          throw new NotificationException("Exception while connecting to the XMPP server", xe);
369       }
370    }
371 }