1 16 package org.outerj.daisy.emailnotifier.serverimpl; 17 18 import org.apache.avalon.framework.logger.AbstractLogEnabled; 19 import org.apache.avalon.framework.service.Serviceable; 20 import org.apache.avalon.framework.service.ServiceManager; 21 import org.apache.avalon.framework.service.ServiceException; 22 import org.apache.avalon.framework.activity.Initializable; 23 import org.apache.avalon.framework.configuration.Configurable; 24 import org.apache.avalon.framework.configuration.Configuration; 25 import org.apache.avalon.framework.configuration.ConfigurationException; 26 import org.apache.xmlbeans.XmlObject; 27 import org.apache.xmlbeans.XmlException; 28 import org.apache.commons.lang.StringUtils; 29 import org.outerj.daisy.emailer.Emailer; 30 import org.outerj.daisy.jms.JmsClient; 31 import org.outerj.daisy.repository.*; 32 import org.outerj.daisy.repository.acl.AclResultInfo; 33 import org.outerj.daisy.repository.user.UserManager; 34 import org.outerj.daisy.repository.user.User; 35 import org.outerj.daisy.repository.user.UserNotFoundException; 36 import org.outerj.daisy.repository.user.Role; 37 import org.outerj.daisy.emailnotifier.EmailSubscriptionManager; 38 import org.outerj.daisy.emailnotifier.Subscriber; 39 import org.outerj.daisy.emailnotifier.serverimpl.formatters.*; 40 import org.outerx.daisy.x10.*; 41 42 import javax.jms.MessageListener ; 43 import javax.jms.Message ; 44 import javax.jms.TextMessage ; 45 import java.util.Map ; 46 import java.util.HashMap ; 47 import java.util.Locale ; 48 import java.util.ArrayList ; 49 import java.lang.reflect.Method ; 50 51 54 public class EmailNotifierImpl extends AbstractLogEnabled implements Serviceable, Initializable, Configurable { 55 private ServiceManager serviceManager; 56 private Repository repository; 57 private String repoUser; 58 private String repoPassword; 59 private Emailer emailer; 60 private JmsClient jmsClient; 61 private String jmsTopicName; 62 private String subscriptionName; 63 private EventListener eventListener = new EventListener(); 64 private String subjectPrefix; 65 private Map mailTemplateFactories; 66 private DocumentURLProviderImpl documentURLProvider = new DocumentURLProviderImpl(); 67 68 72 public void service(ServiceManager serviceManager) throws ServiceException { 73 this.jmsClient = (JmsClient)serviceManager.lookup("jmsclient"); 74 this.serviceManager = serviceManager; 75 } 76 77 public void configure(Configuration configuration) throws ConfigurationException { 78 this.jmsTopicName = configuration.getChild("jmsTopic").getValue(); 79 this.subscriptionName = configuration.getChild("jmsSubscriptionName").getValue(); 80 81 Configuration repositoryUserConf = configuration.getChild("repositoryUser", false); 82 if (repositoryUserConf == null) 83 throw new ConfigurationException("Missing repositoryUser configuration element."); 84 85 repoUser = repositoryUserConf.getAttribute("user"); 86 repoPassword = repositoryUserConf.getAttribute("password"); 87 88 subjectPrefix = configuration.getChild("emailSubjectPrefix").getValue(""); 89 if (subjectPrefix.length() > 0) 90 subjectPrefix += " "; 91 92 Configuration[] documentURLs = configuration.getChild("documentURLs").getChildren("documentURL"); 93 for (int i = 0; i < documentURLs.length; i++) { 94 String collection = documentURLs[i].getAttribute("collection", null); 95 String branch = documentURLs[i].getAttribute("branch", null); 96 String language = documentURLs[i].getAttribute("language", null); 97 String url = documentURLs[i].getAttribute("url"); 98 documentURLProvider.addRule(collection, branch, language, url); 99 } 100 } 101 102 public void initialize() throws Exception { 103 jmsClient.registerDurableTopicListener(jmsTopicName, subscriptionName, eventListener); 104 105 RepositoryManager repositoryManager = (RepositoryManager)serviceManager.lookup("repository-manager"); 106 try { 107 repository = repositoryManager.getRepository(new Credentials(repoUser, repoPassword)); 108 } catch (Throwable e) { 109 throw new Exception ("Problem getting repository.", e); 110 } finally { 111 serviceManager.release(repositoryManager); 112 } 113 114 this.emailer = (Emailer)repository.getExtension("Emailer"); 115 116 mailTemplateFactories = new HashMap (); 117 mailTemplateFactories.put("DocumentVariantCreated", new DocumentVariantCreatedTemplateFactory()); 118 mailTemplateFactories.put("DocumentVariantUpdated", new DocumentVariantUpdatedTemplateFactory()); 119 mailTemplateFactories.put("DocumentVariantDeleted", new DocumentVariantDeletedTemplateFactory()); 120 mailTemplateFactories.put("VersionStateChanged", new VersionStateChangedTemplateFactory()); 122 mailTemplateFactories.put("CommentCreated", new CommentCreatedTemplateFactory()); 123 mailTemplateFactories.put("CommentDeleted", new CommentDeletedTemplateFactory()); 124 } 125 126 class EventListener implements MessageListener { 127 public void onMessage(Message aMessage) { 128 try { 129 TextMessage message = (TextMessage )aMessage; 130 String eventType = message.getStringProperty("type"); 131 132 if (eventType == null) { 133 getLogger().error("Missing type property on JMS message."); 134 return; 135 } 136 137 XmlObject eventDescription = parseEventDescription(message.getText(), eventType); 138 if (eventDescription == null) { 139 return; 140 } 141 142 EmailSubscriptionManager subscriptionManager = (EmailSubscriptionManager)repository.getExtension("EmailSubscriptionManager"); 143 144 try { 145 long documentId = -1; 147 long branchId = -1; 148 long languageId = -1; 149 long[] collectionIds = null; 150 String commentVisibility = null; 151 if (eventType.startsWith("Document")) { 152 DocumentDocument.Document documentXml = null; 153 if (eventType.equals("DocumentVariantCreated")) { 154 documentXml = ((DocumentVariantCreatedDocument)eventDescription).getDocumentVariantCreated().getNewDocumentVariant().getDocument(); 155 } else if (eventType.equals("DocumentVariantUpdated")) { 156 documentXml = ((DocumentVariantUpdatedDocument)eventDescription).getDocumentVariantUpdated().getNewDocumentVariant().getDocument(); 157 } else if (eventType.equals("DocumentVariantDeleted")) { 158 documentXml = ((DocumentVariantDeletedDocument)eventDescription).getDocumentVariantDeleted().getDeletedDocumentVariant().getDocument(); 159 collectionIds = documentXml.getCollectionIds().getCollectionIdArray(); 160 } 163 164 if (documentXml != null) { 165 documentId = documentXml.getId(); 166 branchId = documentXml.getBranchId(); 167 languageId = documentXml.getLanguageId(); 168 } 169 } else if (eventType.equals("VersionStateChanged")) { 170 VersionStateChangedDocument.VersionStateChanged versionStateChangedXml = ((VersionStateChangedDocument)eventDescription).getVersionStateChanged(); 171 documentId = versionStateChangedXml.getDocumentId(); 172 branchId = versionStateChangedXml.getBranchId(); 173 languageId = versionStateChangedXml.getLanguageId(); 174 } else if (eventType.equals("CommentCreated")) { 175 CommentDocument.Comment commentXml = ((CommentCreatedDocument)eventDescription).getCommentCreated().getNewComment().getComment(); 176 documentId = commentXml.getDocumentId(); 177 branchId = commentXml.getBranchId(); 178 languageId = commentXml.getLanguageId(); 179 commentVisibility = commentXml.getVisibility().toString(); 180 } else if (eventType.equals("CommentDeleted")) { 181 CommentDocument.Comment commentXml = ((CommentDeletedDocument)eventDescription).getCommentDeleted().getDeletedComment().getComment(); 182 documentId = commentXml.getDocumentId(); 183 branchId = commentXml.getBranchId(); 184 languageId = commentXml.getLanguageId(); 185 commentVisibility = commentXml.getVisibility().toString(); 186 } 187 188 if (documentId != -1 && collectionIds == null) { 189 try { 190 DocumentCollection[] documentCollections = repository.getDocument(documentId, branchId, languageId, false).getCollections().getArray(); 191 collectionIds = new long[documentCollections.length]; 192 for (int i = 0; i < documentCollections.length; i++) 193 collectionIds[i] = documentCollections[i].getId(); 194 } catch (DocumentNotFoundException e) { 195 return; 197 } catch (DocumentVariantNotFoundException e) { 198 return; 200 } 201 } 202 203 Subscriber[] subscribers; 204 if (eventType.equals("DocumentVariantCreated") || eventType.equals("DocumentVariantDeleted") || eventType.equals("DocumentVariantUpdated") || eventType.equals("DocumentUpdated") || eventType.equals("VersionStateChanged")) { 205 subscribers = subscriptionManager.getAllDocumentEventSubscribers(documentId, branchId, languageId, collectionIds).getArray(); 206 } else if (eventType.startsWith("FieldType") || eventType.startsWith("PartType") || eventType.startsWith("DocumentType")) { 207 subscribers = subscriptionManager.getAllSchemaEventSubscribers().getArray(); 208 } else if (eventType.startsWith("User") || eventType.startsWith("Role")) { 209 subscribers = subscriptionManager.getAllUserEventSubscribers().getArray(); 210 } else if (eventType.startsWith("Collection")) { 211 subscribers = subscriptionManager.getAllCollectionEventSubscribers().getArray(); 212 } else if (eventType.startsWith("Acl")) { 213 subscribers = subscriptionManager.getAllAclEventSubscribers().getArray(); 214 } else if (eventType.startsWith("Comment")) { 215 subscribers = subscriptionManager.getAllCommentEventSubscribers(documentId, branchId, languageId, collectionIds).getArray(); 216 } else { 217 if (getLogger().isDebugEnabled()) 218 getLogger().debug("Unrecognized event type: " + eventType); 219 return; 220 } 221 222 if (subscribers.length > 0) { 223 UserManager userManager = repository.getUserManager(); 224 225 MailTemplate mailTemplate = null; 226 227 for (int i = 0; i < subscribers.length; i++) { 228 long userId = subscribers[i].getUserId(); 229 User user = null; 230 try { 231 user = userManager.getUser(userId, false); 232 } catch (UserNotFoundException e) { 233 subscriptionManager.deleteSubscription(userId); 235 continue; 236 } 237 238 String email = user.getEmail(); 239 if (email == null || email.equals("")) { 240 getLogger().warn("User " + user.getId() + " (" + user.getDisplayName() + ") is subscribed for email notifications but doens't have an email address configured."); 241 continue; 242 } 243 244 if ((eventType.startsWith("User") || eventType.startsWith("Role")) && !user.hasRole(Role.ADMINISTRATOR)) { 245 continue; 246 } 247 248 if (eventType.equals("DocumentVariantDeleted") && !user.hasRole(Role.ADMINISTRATOR)) { 253 continue; 254 } 255 256 boolean isReadAllowed = false; 258 boolean isWriteAllowed = false; 259 if (documentId != -1 && !eventType.equals("DocumentVariantDeleted")) { 260 try { 261 long[] roles = user.getAllRoleIds(); 263 AclResultInfo aclResultInfo = repository.getAccessManager().getAclInfoOnLive(userId, roles, documentId, branchId, languageId); 264 if (aclResultInfo.isAllowed(org.outerj.daisy.repository.acl.AclPermission.WRITE)) { 265 isReadAllowed = true; 266 isWriteAllowed = true; 267 } 268 if (aclResultInfo.isAllowed(org.outerj.daisy.repository.acl.AclPermission.READ)) { 269 isReadAllowed = true; 270 } 271 } catch (DocumentNotFoundException e) { 272 continue; 274 } catch (DocumentVariantNotFoundException e) { 275 continue; 277 } 278 if (!isReadAllowed) 279 continue; } 281 282 if (eventType.startsWith("Comment")) { 283 if (commentVisibility.equals("private")) { 284 continue; 286 } 287 if (commentVisibility.equals("editors") && !isWriteAllowed) { 288 continue; 289 } 290 } 291 292 if (mailTemplate == null) 293 mailTemplate = getMailTemplate(eventType, eventDescription); 294 295 Locale locale = subscribers[i].getLocale(); 296 if (locale == null) 297 locale = Locale.US; 298 emailer.send(email, subjectPrefix + mailTemplate.getSubject(locale), mailTemplate.getMessage(locale)); 299 } 300 } 301 } finally { 302 cleanupSubscriptions(eventType, eventDescription, subscriptionManager); 303 } 304 } catch (Throwable e) { 305 getLogger().error("Error processing JMS event.", e); 306 } 307 } 308 } 309 310 private void cleanupSubscriptions(String eventType, XmlObject eventDescription, EmailSubscriptionManager subscriptionManager) { 311 try { 312 if (eventType.equals("DocumentDeleted")) { 313 DocumentDocument.Document documentXml = ((DocumentDeletedDocument)eventDescription).getDocumentDeleted().getDeletedDocument().getDocument(); 314 subscriptionManager.deleteAllSubscriptionsForDocument(documentXml.getId()); 315 } if (eventType.equals("DocumentVariantDeleted")) { 316 DocumentDocument.Document documentXml = ((DocumentVariantDeletedDocument)eventDescription).getDocumentVariantDeleted().getDeletedDocumentVariant().getDocument(); 317 VariantKey variantKey = new VariantKey(documentXml.getId(), documentXml.getBranchId(), documentXml.getLanguageId()); 318 subscriptionManager.deleteAllSubscriptionsForDocumentVariant(variantKey); 319 } else if (eventType.equals("CollectionDeleted")) { 320 CollectionDeletedDocument collectionDeletedDocument = (CollectionDeletedDocument)eventDescription; 321 long id = collectionDeletedDocument.getCollectionDeleted().getDeletedCollection().getCollection().getId(); 322 subscriptionManager.deleteAllSubscriptionsForCollection(id); 323 } 324 } catch (Throwable e) { 325 getLogger().error("Error in subscription cleanup handling.", e); 326 } 327 } 328 329 interface EventParser { 330 XmlObject parse(String data) throws XmlException; 331 } 332 333 private static final Map EVENT_PARSERS = new HashMap (); 334 static { 335 try { 336 EVENT_PARSERS.put("DocumentDeleted", DocumentDeletedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 338 EVENT_PARSERS.put("DocumentVariantCreated", DocumentVariantCreatedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 339 EVENT_PARSERS.put("DocumentVariantUpdated", DocumentVariantUpdatedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 340 EVENT_PARSERS.put("DocumentVariantDeleted", DocumentVariantDeletedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 341 EVENT_PARSERS.put("FieldTypeCreated", FieldTypeCreatedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 342 EVENT_PARSERS.put("FieldTypeUpdated", FieldTypeUpdatedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 343 EVENT_PARSERS.put("FieldTypeDeleted", FieldTypeDeletedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 344 EVENT_PARSERS.put("PartTypeCreated", PartTypeCreatedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 345 EVENT_PARSERS.put("PartTypeUpdated", PartTypeUpdatedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 346 EVENT_PARSERS.put("PartTypeDeleted", PartTypeDeletedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 347 EVENT_PARSERS.put("DocumentTypeCreated", DocumentTypeCreatedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 348 EVENT_PARSERS.put("DocumentTypeUpdated", DocumentTypeUpdatedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 349 EVENT_PARSERS.put("DocumentTypeDeleted", DocumentTypeDeletedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 350 EVENT_PARSERS.put("UserCreated", UserCreatedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 351 EVENT_PARSERS.put("UserUpdated", UserUpdatedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 352 EVENT_PARSERS.put("UserDeleted", UserDeletedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 353 EVENT_PARSERS.put("RoleCreated", RoleCreatedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 354 EVENT_PARSERS.put("RoleUpdated", RoleUpdatedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 355 EVENT_PARSERS.put("RoleDeleted", RoleDeletedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 356 EVENT_PARSERS.put("CollectionCreated", CollectionCreatedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 357 EVENT_PARSERS.put("CollectionUpdated", CollectionUpdatedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 358 EVENT_PARSERS.put("CollectionDeleted", CollectionDeletedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 359 EVENT_PARSERS.put("AclUpdated", AclUpdatedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 360 EVENT_PARSERS.put("CommentCreated", CommentCreatedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 361 EVENT_PARSERS.put("CommentDeleted", CommentDeletedDocument.Factory.class.getMethod("parse", new Class [] { String .class })); 362 } catch (Exception e) { 363 throw new RuntimeException ("Error initializing event parsers map.", e); 364 } 365 } 366 367 368 private XmlObject parseEventDescription(String data, String eventType) { 369 try { 370 Method parseMethod = (Method )EVENT_PARSERS.get(eventType); 371 if (parseMethod != null) { 372 return (XmlObject)parseMethod.invoke(null, new Object [] {data}); 373 } 374 } catch (Exception e) { 375 getLogger().error("Error parsing event description XML.", e); 376 } 377 return null; 378 } 379 380 private MailTemplate getMailTemplate(String eventType, XmlObject eventDescription) throws Exception { 381 MailTemplateFactory factory = (MailTemplateFactory)mailTemplateFactories.get(eventType); 382 if (factory != null) { 383 return factory.createMailTemplate(eventDescription, repository, documentURLProvider); 384 } else { 385 return new DummyMailTemplate(eventType, eventDescription); 386 } 387 } 388 389 class DocumentURLProviderImpl implements DocumentURLProvider { 390 private ArrayList documentURLRules = new ArrayList (); 391 392 public String getURL(Document document) { 393 String branch = null; 394 String language = null; 395 try { 396 branch = repository.getVariantManager().getBranch(document.getBranchId(), false).getName(); 397 language = repository.getVariantManager().getLanguage(document.getLanguageId(), false).getName(); 398 } catch (RepositoryException e) { 399 return null; 401 } 402 for (int i = 0; i < documentURLRules.size(); i++) { 403 DocumentURLRule rule = (DocumentURLRule)documentURLRules.get(i); 404 boolean variantMatch = (rule.branch == null || branch.equals(rule.branch)) 405 && (rule.language == null || language.equals(rule.language)); 406 if (variantMatch) { 407 if (rule.collection == null) { 408 return rule.getURL(document.getId(), document.getBranchId(), document.getLanguageId()); 409 } else { 410 DocumentCollection[] collections = document.getCollections().getArray(); 411 for (int k = 0; k < collections.length; k++) { 412 if (collections[k].getName().equals(rule.collection)) { 413 return rule.getURL(document.getId(), document.getBranchId(), document.getLanguageId()); 414 } 415 } 416 } 417 } 418 } 419 return null; 420 } 421 422 public void addRule(String collection, String branch, String language, String URL) { 423 DocumentURLRule rule = new DocumentURLRule(); 424 rule.collection = collection; 425 rule.branch = branch; 426 rule.language = language; 427 rule.URL = URL; 428 documentURLRules.add(rule); 429 } 430 431 class DocumentURLRule { 432 public String collection; 433 public String branch; 434 public String language; 435 public String URL; 436 437 public String getURL(long documentId, long branchId, long languageId) { 438 String result = StringUtils.replace(URL, "{id}", String.valueOf(documentId)); 439 result = StringUtils.replace(result, "{branch}", String.valueOf(branchId)); 440 result = StringUtils.replace(result, "{language}", String.valueOf(languageId)); 441 return result; 442 } 443 } 444 } 445 446 } | Popular Tags |