1
2
3
4
5
6
7
8 /
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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
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
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 }