View Javadoc

1   //
2   //Copyright (c) 2003, Caltha - Gajda, Krzewski, Mach, Potempski Sp.J. 
3   //All rights reserved. 
4   // 
5   //Redistribution and use in source and binary forms, with or without modification,  
6   //are permitted provided that the following conditions are met: 
7   // 
8   //* Redistributions of source code must retain the above copyright notice,  
9   //this list of conditions and the following disclaimer. 
10  //* Redistributions in binary form must reproduce the above copyright notice,  
11  //this list of conditions and the following disclaimer in the documentation  
12  //and/or other materials provided with the distribution. 
13  //* Neither the name of the Caltha - Gajda, Krzewski, Mach, Potempski Sp.J.  
14  //nor the names of its contributors may be used to endorse or promote products  
15  //derived from this software without specific prior written permission. 
16  //
17  //THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"  
18  //AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED  
19  //WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
20  //IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,  
21  //INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,  
22  //BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
23  //OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  
24  //WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)  
25  //ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE  
26  //POSSIBILITY OF SUCH DAMAGE. 
27  //
28  
29  package org.objectledge.mail;
30  
31  import java.io.ByteArrayInputStream;
32  import java.io.InputStream;
33  import java.lang.reflect.Constructor;
34  import java.net.MalformedURLException;
35  import java.util.ArrayList;
36  import java.util.Collection;
37  import java.util.HashMap;
38  import java.util.Hashtable;
39  import java.util.Iterator;
40  import java.util.List;
41  import java.util.Locale;
42  import java.util.Map;
43  import java.util.Vector;
44  
45  import javax.mail.Folder;
46  import javax.mail.Message;
47  import javax.mail.MessagingException;
48  import javax.mail.Session;
49  import javax.mail.Store;
50  import javax.mail.internet.MimeMessage;
51  
52  import org.apache.xmlrpc.XmlRpcClient;
53  import org.apache.xmlrpc.XmlRpcException;
54  import org.jcontainer.dna.Configuration;
55  import org.jcontainer.dna.Logger;
56  import org.objectledge.utils.StringUtils;
57  
58  /**
59   * Mailman mailing list manager implementation.
60   * 
61   * @author <a href="mailto:pablo@caltha.pl">Pawel Potempski </a>
62   * @version $Id: MailmanMailingListsManager.java,v 1.32 2006/05/15 11:57:23 rafal Exp $
63   */
64  public class MailmanMailingListsManager implements MailingListsManager
65  {
66      /*** List-Id header as defined by RFC2919 */
67      private static final String LIST_ID_HEADER_NAME = "List-Id";
68      
69      /*** List-Post header as defined by RFC2369 */
70      private static final String LIST_POST_HEADER_NAME = "List-Post";
71      
72      /*** logging facility */
73      private Logger logger;
74      
75      /*** mail system */
76      private MailSystem mailSystem;
77  
78      /*** mailman rcp address */
79      private String address;
80  
81      /*** mailman admin password */
82      private String adminPassword;
83      
84      /*** rpc client */
85      private XmlRpcClient client;
86      
87      /*** last id map */
88      private HashMap<String, Integer> lastIdMap;
89  
90      /*** system monitoring address */
91      private String monitoringAddress;
92      
93      /*** system monitoring mail session */
94      private String monitoringSessionName;
95      
96      /***
97       * Ledge component constructor.
98       * 
99       * @param config component configuration.
100      * @param logger the logger.
101      */
102     public MailmanMailingListsManager(Configuration config, Logger logger, MailSystem mailSystem)
103         throws MalformedURLException
104     {
105         this.logger = logger;
106         this.mailSystem = mailSystem;
107         address = config.getChild("address").getValue("http://localhost/mailman/RPC2");
108         adminPassword = config.getChild("password").getValue("secret");
109         monitoringAddress = config.getChild("monitoring_address").getValue("");
110         monitoringSessionName = config.getChild("monitoring_session").getValue("");
111         lastIdMap = new HashMap<String, Integer>();
112         client = new XmlRpcClient(address);
113     }
114     
115     /***
116      * Standalone constructor.
117      * 
118      * @param logger the logger.
119      * @param address mailman rcp address.
120      * @param login mailman admin login.
121      * @param password mailman admin password.
122      */
123     public MailmanMailingListsManager(Logger logger, String address, String password)
124         throws MalformedURLException
125     {
126         this.logger = logger;
127         this.address = address;
128         this.adminPassword = password;
129         lastIdMap = new HashMap<String, Integer>();
130         client = new XmlRpcClient(address);
131     }
132     
133     public MailingListsManager.Status getStatus()
134     {
135         try
136         {
137             getAvailableLocales();
138             checkMessageStore();
139             return MailingListsManager.Status.OPERATIONAL;
140         }
141         catch(MailingListsException e)
142         {
143             logger.error("The manager is not operational", e);
144             return MailingListsManager.Status.UNOPERATIONAL;
145         }
146     }
147 
148     /***
149      * {@inheritDoc}
150      */
151     public String createList(String name, String domain, 
152         String[] administrators, String password, 
153         boolean notify, Locale locale, boolean moderated) throws MailingListsException
154     {
155         Object[] params = new Object[]{
156             adminPassword, name, domain, "", true, administrators,
157             password, notify, vector(locale.toString())};
158         Object result = null;
159         result = executeMethod("Mailman.createList", params);
160         if(result == null)
161         {
162             throw new MailingListsException("Null result of rpc method invocation");
163         }
164         if(result instanceof String)
165         {
166             String newPassword = (String)result;
167             MailingList list = getList(name, newPassword); 
168             if(monitoringAddress.length() > 0)
169             {
170                 list.addMember(monitoringAddress, "Mailiman - Ledge integration", "", false, true,
171                     false, false);
172             }
173             list.setPostingModerated(moderated);
174             return newPassword;
175         }
176         throw new MailingListsException("Invalid result class: "+result.getClass().getName());
177     }
178 
179     /***
180      * {@inheritDoc}
181      */
182     public void deleteList(String name, boolean deleteArchives) throws MailingListsException
183     {
184         Object[] params = new Object[]{
185                         adminPassword, name, deleteArchives};
186         Object result = executeMethod("Mailman.deleteList", params);
187         if(result instanceof Boolean)
188         {
189             if(!((Boolean)result).booleanValue())
190             {
191                 throw new MailingListsException("failed to delete list - result false");
192             }
193             return;
194         }
195         if(result == null)
196         {
197             throw new MailingListsException("Null result of rpc method invocation");
198         }
199         throw new MailingListsException("Invalid result class: "+result.getClass().getName());
200     }
201 
202     /***
203      * {@inheritDoc}
204      */
205     public MailingList getList(String name, String password) throws MailingListsException
206     {
207         return new MailmanMailingList(this, name, password);
208     }
209 
210     /***
211      * {@inheritDoc}
212      */
213     public MailingList getList(String name) throws MailingListsException
214     {
215         return new MailmanMailingList(this, name, adminPassword);
216     }
217     
218     /***
219      * {@inheritDoc}
220      */
221     public List<String> getLists() throws MailingListsException
222     {
223         return getLists("");
224     }
225     
226     private List<String> getLists(String filter) throws MailingListsException
227     {
228         Object[] params = new Object[]{
229                         adminPassword, filter};
230         Object result = executeMethod("Mailman.listAllLists", params);
231         if(result instanceof List)
232         {
233             List<List<String>> infos = (List<List<String>>)result;
234             List<String> names = new ArrayList<String>(infos.size());
235             for(List<String> info : infos)
236             {
237                 names.add(info.get(0));
238             }
239             return names;
240         }
241         if(result == null)
242         {
243             throw new MailingListsException("Null result of rpc method invocation");
244         }
245         throw new MailingListsException("Invalid result class: "+result.getClass().getName());
246     }
247 
248     /***
249      * {@inheritDoc}
250      */
251     public List<String> getPublicLists() throws MailingListsException
252     {
253         return getPublicLists("");
254     }
255     
256     /***
257      * {@inheritDoc}
258      */
259     public List<String> getPublicLists(String filter) throws MailingListsException
260     {
261         Object[] params = new Object[]{filter};
262         Object result = executeMethod("Mailman.listAdvertisedLists", params);
263         List<String> names = new ArrayList<String>();
264         if(result instanceof List)
265         {
266             for(List<String> el: ((List<List<String>>)result))
267             {
268                 names.add(el.get(0));
269             }
270             return names;
271         }
272         if(result == null)
273         {
274             throw new MailingListsException("Null result of rpc method invocation");
275         }
276         throw new MailingListsException("Invalid result class: "+result.getClass().getName());
277     }
278 
279     /***
280      * {@inheritDoc}
281      */    
282     public List<Locale> getAvailableLocales()
283         throws MailingListsException
284     {
285         Object[] params = new Object[] { adminPassword };
286         Object result = executeMethod("Mailman.getAvailableLocales", params);
287         List<String> names = new ArrayList<String>();
288         if(result instanceof Collection)
289         {
290             Collection list = (Collection)result;
291             ArrayList<Locale> codes = new ArrayList<Locale>();
292             Iterator it = list.iterator();
293             while(it.hasNext())
294             {
295                 List innerList = (List)it.next();
296                 codes.add(StringUtils.getLocale((String)innerList.get(0)));
297             }
298             return codes;
299         }
300         if(result == null)
301         {
302             throw new MailingListsException("Null result of rpc method invocation");
303         }
304         throw new MailingListsException("Invalid result class: "+result.getClass().getName());
305     }
306 
307     /***
308      * {@inheritDoc}
309      */
310     public List<String> getAvailableDomains()
311         throws MailingListsException
312     {
313         Object[] params = new Object[] { adminPassword };
314         Object result = executeMethod("Mailman.getAvailableDomains", params);        
315         List<String> names = new ArrayList<String>();
316         if(result instanceof Collection)
317         {
318             Collection list = (Collection)result;
319             ArrayList<String> domains = new ArrayList<String>();
320             Iterator it = list.iterator();
321             while(it.hasNext())
322             {
323                 domains.add((String)it.next());
324             }
325             return domains;
326         }
327         if(result == null)
328         {
329             throw new MailingListsException("Null result of rpc method invocation");
330         }
331         throw new MailingListsException("Invalid result class: "+result.getClass().getName());
332     }
333     
334     private void checkMessageStore()
335         throws MailingListsException
336     {
337         if(monitoringAddress.length() > 0)
338         {
339             Session session = mailSystem.getSession(monitoringSessionName);
340             try
341             {
342                 Store store = session.getStore();
343                 store.connect();
344                 try
345                 {
346                     Folder folder = store.getFolder("INBOX");
347                     folder.open(Folder.READ_WRITE);
348                     try
349                     {
350                         folder.getMessages();
351                     }
352                     finally
353                     {
354                         folder.close(true);
355                     }
356                 }
357                 finally
358                 {
359                     store.close();
360                 }
361             }
362             catch(Exception e)
363             {
364                 throw new MailingListsException("failed to check new messages", e);
365             }
366         }
367     }
368     
369     /***
370      * {@inheritDoc}
371      */    
372     public Store getMessageStore() throws MailingListsException
373     {
374         try
375         {
376             Session session = mailSystem.getSession(monitoringSessionName);
377             return session.getStore();
378         }
379         catch(Exception e)
380         {
381             throw new MailingListsException("failed to access message store", e);
382         }
383     }
384 
385     /***
386      * Dedect which mailing list the message belongs to.
387      * 
388      * Unfortunately Mailman is not accepting the List-Id headers it generates as list 
389      * identifiers - it creating them by joing local list name (internal identifier) with list 
390      * domain using a dot, while at the same time it accepts dots in local list names. This makes 
391      * it impossible to extract local list name from the header in a reliable way. On the other
392      * hand the List-Post header contains always <mailto:LOCALNAME@DOMAIN> which may be parsed
393      * reliably. Just make sure the list is configured to put this header in the messages.
394      * 
395      * @param message the message.
396      * @return the list name.
397      * @throws MessagingException if there is a problem parsing message headers.
398      */
399     private String getListName(Message message) throws MessagingException
400     {
401         String[] listPostHeader = message.getHeader(LIST_POST_HEADER_NAME);
402         if(listPostHeader != null && listPostHeader.length > 0)
403         {
404             String header = listPostHeader[0]; 
405             if(header.contains("<mailto:") && header.contains(">"))
406             {
407                 int startIndex = header.lastIndexOf("<mailto:");
408                 int endIndex = header.lastIndexOf("@");
409                 return header.substring(startIndex+8, endIndex);
410             }
411         }
412         return null;
413     }
414 
415     /***
416      * {@inheritDoc}
417      */
418     public MailingList getList(Message message) throws MailingListsException
419     {
420         try
421         {
422             String listName = getListName(message);
423             if(listName != null)
424             {
425                 return getList(listName);
426             }
427         }
428         catch(MessagingException e)
429         {
430             logger.error("failed to parse message", e);
431         }
432         return null;
433     }
434     
435     // package private operations
436     
437     /***
438      * {@inheritDoc}
439      */
440     MailingList.OperationStatus addMember(String listName, String adminPassword, 
441         String address, String name, String password, 
442         boolean digest, boolean ignoreCreationPolicy, boolean acknowledge, boolean notifyAdmins)
443             throws MailingListsException
444     {
445         Object[] params = new Object[] { listName, adminPassword, address, name, password, digest,
446                         ignoreCreationPolicy, acknowledge, notifyAdmins };
447         Object result = null;
448         try
449         {
450             result = executeMethod("Mailman.addMember", params);
451         }
452         catch(NeedApprovalException e)
453         {
454             return MailingList.OperationStatus.NEEDS_APPROVAL;
455         }
456         catch(NeedConfirmationException e)
457         {
458             return MailingList.OperationStatus.NEEDS_CONFIRMATION;
459         }
460         if(result instanceof Boolean)
461         {
462             if(((Boolean)result))
463             {
464                 return MailingList.OperationStatus.COMPLETED;
465             }
466         }
467         if(result == null)
468         {
469             throw new MailingListsException("Null result of rpc method invocation");
470         }
471         throw new MailingListsException("Invalid result class: "+result.getClass().getName());
472     }
473 
474     /***
475      * {@inheritDoc}
476      */
477     void changeMemberAddress(String listName, String adminPassword, 
478         String oldAddress, String newAddress, boolean keepOld) throws MailingListsException
479     {
480         Object[] params = new Object[]{
481                         listName, adminPassword, address, oldAddress, 
482                         newAddress, keepOld};
483         Object result = executeMethod("Mailman.changeMemberAddress", params);
484         if(result instanceof Boolean)
485         {
486             if(((Boolean)result))
487             {
488                return;
489             }
490         }
491         if(result == null)
492         {
493             throw new MailingListsException("Null result of rpc method invocation");
494         }
495         throw new MailingListsException("Invalid result class: "+result.getClass().getName());
496     }
497 
498     /***
499      * {@inheritDoc}
500      */
501     MailingList.OperationStatus deleteMember(String listName, String adminPassword, String address,
502         boolean ignoreDeletingPolicy, boolean acknowledge, boolean notifyAdmins)
503         throws MailingListsException
504     {
505         if(address.equals(monitoringAddress))
506         {
507             throw new MailingListsException("monitoring account cannot be unsubscribed from a list");
508         }
509         Object[] params = new Object[] { listName, adminPassword, address, ignoreDeletingPolicy,
510                         acknowledge, notifyAdmins };
511         Object result = null;
512         try
513         {
514             result = executeMethod("Mailman.deleteMember", params);
515         }
516         catch(NeedApprovalException e)
517         {
518             return MailingList.OperationStatus.NEEDS_APPROVAL;
519         }
520         catch(NeedConfirmationException e)
521         {
522             return MailingList.OperationStatus.NEEDS_CONFIRMATION;
523         }
524         if(result instanceof Boolean)
525         {
526             if(((Boolean)result))
527             {
528                 return MailingList.OperationStatus.COMPLETED;
529             }
530         }
531         if(result == null)
532         {
533             throw new MailingListsException("Null result of rpc method invocation");
534         }
535         throw new MailingListsException("Invalid result value: '"+result+
536             "' or class: "+result.getClass().getName());    
537     }
538 
539     /***
540      * {@inheritDoc}
541      */
542     List<String> getMembers(String listName, String adminPassword)
543         throws MailingListsException
544     {
545         Object[] params = new Object[]{listName, adminPassword};
546         Object result = executeMethod("Mailman.getMembers", params);
547         if(result instanceof List)
548         {
549             List<String> members = (List<String>)result;
550             members.remove(monitoringAddress);
551             return members;
552         }
553         if(result == null)
554         {
555             throw new MailingListsException("Null result of rpc method invocation");
556         }
557         throw new MailingListsException("Invalid result class: "+result.getClass().getName());
558     }
559 
560     /***
561      * {@inheritDoc}
562      */
563     Object getOption(String listName, String adminPassword, String key)
564         throws MailingListsException
565     {
566         Object[] params = new Object[]{
567                         listName, adminPassword, new String[]{key}};
568         Object result = executeMethod("Mailman.getOptions", params);
569         if(result instanceof Map)
570         {
571             return ((Map)result).get(key);
572         }
573         if(result == null)
574         {
575             throw new MailingListsException("Null result of rpc method invocation");
576         }
577         throw new MailingListsException("Invalid result value: '"+result+"' or class: "+result.getClass().getName());    
578     }
579 
580     /***
581      * {@inheritDoc}
582      */
583     void setOption(String listName, String adminPassword, 
584         String key, Object value) throws MailingListsException
585     {
586         Hashtable<String, Object> map = new Hashtable<String, Object>();
587         map.put(key, value);
588         Object[] params = new Object[]{
589                         listName, adminPassword, map};
590         Object result = executeMethod("Mailman.setOptions", params);
591         if(result instanceof Boolean)
592         {
593             if(((Boolean)result))
594             {
595                 return;
596             }
597         }
598         if(result == null)
599         {
600             throw new MailingListsException("Null result of rpc method invocation");
601         }
602         throw new MailingListsException("Invalid result value: '"+result+"' or class: "+result.getClass().getName());
603     }
604     
605     void addOptionValue(String listName, String adminPassword, String key, Object value)
606         throws MailingListsException
607     {
608         List values = (List)getOption(listName, adminPassword, key);
609         values.add(value);
610         setOption(listName, adminPassword, key, values);
611     }
612 
613     void removeOptionValue(String listName, String adminPassword, String key, Object value)
614         throws MailingListsException
615     {
616         List values = (List)getOption(listName, adminPassword, key);
617         values.remove(value);
618         setOption(listName, adminPassword, key, values);
619     }
620     
621     /***
622      * {@inheritDoc}
623      */
624     String setPassword(String listName, String adminPassword, 
625         String password) throws MailingListsException
626     {
627         Object[] params = new Object[]{listName, adminPassword, password};
628         Object result = executeMethod("Mailman.resetListPassword", params);
629         if(result instanceof String)
630         {
631             return ((String)result);
632         }
633         if(result == null)
634         {
635             throw new MailingListsException("Null result of rpc method invocation");
636         }
637         throw new MailingListsException("Invalid result class: "+result.getClass().getName());
638     }
639     
640     /***
641      * {@inheritDoc}
642      */
643     List<String> getPendingPosts(String listName, String adminPassword) throws MailingListsException
644     {
645         Object[] params = new Object[]{listName, adminPassword};
646         Object result = executeMethod("Mailman.getPendingMessages", params);
647         if(result instanceof List)
648         {
649             return toStringList((List<Integer>)result);
650         }
651         if(result == null)
652         {
653             throw new MailingListsException("Null result of rpc method invocation");
654         }
655         throw new MailingListsException("Invalid result class: "+result.getClass().getName());
656     }
657 
658     /***
659      * {@inheritDoc}
660      */
661     List<String> getPendingSubscriptions(String listName, String adminPassword)
662         throws MailingListsException
663     {
664         Object[] params = new Object[]{listName, adminPassword};
665         Object result = executeMethod("Mailman.getPendingSubscriptions", params);
666         if(result instanceof List)
667         {
668             return toStringList((List<Integer>)result);
669         }
670         if(result == null)
671         {
672             throw new MailingListsException("Null result of rpc method invocation");
673         }
674         throw new MailingListsException("Invalid result class: "+result.getClass().getName());
675     }
676 
677     /***
678      * {@inheritDoc}
679      */
680     List<String> getPendingUnsubscriptions(String listName, String adminPassword)
681         throws MailingListsException
682     {
683         Object[] params = new Object[]{listName, adminPassword};
684         Object result = executeMethod("Mailman.getPendingUnsubscriptions", params);
685         if(result instanceof List)
686         {
687             return toStringList((List<Integer>)result);
688         }
689         if(result == null)
690         {
691             throw new MailingListsException("Null result of rpc method invocation");
692         }
693         throw new MailingListsException("Invalid result class: "+result.getClass().getName());
694     }    
695 
696     List<String> getNewPendingTasks(String listName, String adminPassword)
697         throws MailingListsException
698     {
699         int lastId = getLastId(listName);
700         Object[] params = new Object[]{listName, adminPassword, lastId};
701         Object result = executeMethod("Mailman.getNewPendingTasks", params);
702         if(result instanceof List)
703         {
704             List list = (List)result;
705             if(!list.isEmpty())
706             {
707                 setLastId(listName, (Integer)list.get(list.size() - 1));
708             }
709             return toStringList(list);
710         }
711         if(result == null)
712         {
713             throw new MailingListsException("Null result of rpc method invocation");
714         }
715         throw new MailingListsException("Invalid result class: "+result.getClass().getName());
716     }
717 
718     Message getPendingTask(String listName, String adminPassword, String id)
719         throws MailingListsException
720     {
721         Object[] params = new Object[]{listName, adminPassword, Integer.parseInt(id)};
722         Object result = executeMethod("Mailman.getPendingTask", params);
723         if(result instanceof String)
724         {
725             String msg = (String)result;
726             Session session = mailSystem.getSession();
727             try 
728             {
729                 InputStream is = new ByteArrayInputStream(msg.getBytes("UTF-8"));
730                 MimeMessage message = new MimeMessage(session, is);
731                 return message;
732             }
733             catch(Exception e)
734             {
735                 throw new MailingListsException("Failed to deserialize message", e);
736             }
737         }
738         if(result == null)
739         {
740             throw new MailingListsException("Null result of rpc method invocation");
741         }
742         throw new MailingListsException("Invalid result class: "+result.getClass().getName());
743     }
744         
745     Integer getPendingTaskType(String listName, String adminPassword, String id)
746         throws MailingListsException
747     {
748         Object[] params = new Object[]{listName, adminPassword, Integer.parseInt(id)};
749         Object result = executeMethod("Mailman.getPendingTaskType", params);
750         if(result instanceof Integer)
751         {
752             return (Integer)result;
753         }
754         if(result == null)
755         {
756             throw new MailingListsException("Null result of rpc method invocation");
757         }
758         throw new MailingListsException("Invalid result class: "+result.getClass().getName());
759     }
760     
761     void handleModeratorRequest(String listName, String adminPassword,
762         String id, int command, String comment)
763             throws MailingListsException
764     {
765         Object[] params;
766         if(comment != null)
767         {
768             params = new Object[]{listName, adminPassword, Integer.parseInt(id), command};
769         }
770         else
771         {
772             params = new Object[]{listName, adminPassword, Integer.parseInt(id), command, comment};            
773         }
774         Object result = executeMethod("Mailman.handleModeratorRequest", params);
775         if(result instanceof Boolean && ((Boolean)result))
776         {
777             return;
778         }
779         if(result == null)
780         {
781             throw new MailingListsException("Null result of rpc method invocation");
782         }
783         throw new MailingListsException("Invalid result class: "+result.getClass().getName());
784     }
785     
786     void postMessage(String listName, String adminPassword, String message)
787         throws MailingListsException
788     {
789         Object[] params = new Object[] { listName, adminPassword, message };
790         Object result = executeMethod("Mailman.postMessage", params);
791         if(result instanceof Boolean && ((Boolean)result))
792         {
793             return;
794         }
795         if(result == null)
796         {
797             throw new MailingListsException("Null result of rpc method invocation");
798         }
799         throw new MailingListsException("Invalid result class: " + result.getClass().getName());
800     }
801 
802     String getInterfaceBaseURL(String domain)
803         throws MailingListsException
804     {
805         Object[] params = new Object[] { adminPassword, domain };
806         Object result = executeMethod("Mailman.getInterfaceBaseURL", params);
807         if(result instanceof String)
808         {
809             return (String)result;
810         }
811         if(result == null)
812         {
813             throw new MailingListsException("Null result of rpc method invocation");
814         }
815         throw new MailingListsException("Invalid result class: "+result.getClass().getName());
816     }
817     
818     // -- private methods -----------------------------------------------------------------------
819     
820     /***
821      * RPC method executor.
822      * It resolve XmlRpcException codes into Java Exceptions.
823      * 
824      * @param method name of the method to call.
825      * @param parameters arguments of the method.
826      * @return result of method invocation.
827      */
828     private Object executeMethod(String method, Object[] parameters)
829         throws MailingListsException
830     {
831         Object result = null;
832         try
833         {
834             result = client.execute(method, getParameters(parameters));
835         }
836         catch(Exception e)
837         {
838             throw new MailingListsException("failed to invoke rpc method", e);
839         }
840         if(result instanceof XmlRpcException)
841         {
842             throw unwrapException((XmlRpcException)result);
843         }
844         return result;
845     }
846     
847     private static final Map<Integer,Class<? extends MailingListsException>> exceptions = 
848         new HashMap<Integer,Class<? extends MailingListsException>>();
849     
850     static
851     {
852         exceptions.put(-32501, NeedConfirmationException.class);
853         exceptions.put(-32502, NeedApprovalException.class);
854         exceptions.put(-32503, MailingListsAuthenticationException.class);
855         exceptions.put(-32504, InvalidListNameException.class);
856         exceptions.put(-32505, ListAlreadyExistsException.class);
857         exceptions.put(-32506, LostAdministrativeRequestException.class);
858     }
859     
860     private MailingListsException unwrapException(XmlRpcException xmlRpcEx)
861     {
862         Class<? extends MailingListsException> exClass = exceptions.containsKey(xmlRpcEx.code) ? 
863             exceptions.get(xmlRpcEx.code) : MailingListsException.class;
864         try
865         {
866             Constructor<? extends MailingListsException> exCtor = exClass
867                 .getConstructor(new Class[] { String.class });
868             return exCtor.newInstance(xmlRpcEx.getMessage());
869         }
870         catch(Exception e)
871         {
872             return new MailingListsException("failed to reflecively report exception of class "
873                 + exClass.getName(), e); 
874         }
875     }
876     
877     /***
878      * Convert list of objects into vector.
879      * 
880      * @param params list of objects.
881      * @return vector of objects.
882      */
883     private Vector getParameters(Object[] params)
884     {
885         Vector vector = new Vector();
886         for(Object ob: params)
887         {
888             vector.add(ob);
889         }
890         return vector;
891     }
892     
893     private Vector vector(Object ... params)
894     {
895         Vector vector = new Vector();
896         for(Object obj: params)
897         {
898             vector.add(obj);
899         }
900         return vector;        
901     }
902     
903     private List<String> toStringList(List<Integer> in)
904     {
905         List<String> out = new ArrayList<String>(in.size());
906         for(Integer i : in)
907         {
908             out.add(i.toString());
909         }
910         return out;
911     }    
912     
913     private synchronized int getLastId(String listName)
914     {
915         Integer lastId = lastIdMap.get(listName);
916         if(lastId != null)
917         {
918             return lastId;
919         }
920         lastIdMap.put(listName, 0);
921         return 0;
922     }
923     
924     private synchronized void setLastId(String listName, int value)
925     {
926         lastIdMap.put(listName, value);
927     }
928 }