/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.portlet.forums.util;
import java.io.StringWriter;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.Set;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.mail.Address;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.Message.RecipientType;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.portlet.ActionResponse;
import javax.rmi.PortableRemoteObject;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.apache.log4j.Logger;
import org.jboss.portal.common.transaction.Transactions;
import org.jboss.portal.format.render.bbcodehtml.ToTextRenderer;
import org.jboss.portal.format.template.TemplateLoader;
import org.jboss.portal.identity.IdentityException;
import org.jboss.portal.identity.User;
import org.jboss.portal.identity.UserProfileModule;
import org.jboss.portlet.forums.ForumsConstants;
import org.jboss.portlet.forums.ForumsModule;
import org.jboss.portlet.forums.auth.Authorization;
import org.jboss.portlet.forums.auth.AuthorizationInterface;
import org.jboss.portlet.forums.auth.JSFUIContext;
import org.jboss.portlet.forums.model.Category;
import org.jboss.portlet.forums.model.Forum;
import org.jboss.portlet.forums.model.Message;
import org.jboss.portlet.forums.model.Post;
import org.jboss.portlet.forums.model.Topic;
import org.jboss.portlet.forums.model.TopicWatch;
import org.jboss.portlet.forums.model.Watch;
import org.jboss.portlet.forums.ui.Constants;
import org.jboss.portlet.forums.ui.PortalUtil;
import EDU.oswego.cs.dl.util.concurrent.LinkedQueue;
import EDU.oswego.cs.dl.util.concurrent.QueuedExecutor;
/**
* @author <a href="mailto:julien@jboss.org">Julien Viet</a>
* @author <a href="mailto:ryszard.kozmik@jboss.com">Ryszard Kozmik</a>
*/
public class NotificationEngine
{
// Types of post
public static final int MODE_POST = 0;
public static final int MODE_REPLY = 1;
public static final int MODE_REPOST = 2;
private ForumsModule forumsModule;
private UserProfileModule userProfileModule;
private TemplateLoader mailTemplates;
private String from;
private QueuedExecutor executor;
private TransactionManager tm;
private final Logger log = Logger.getLogger(NotificationEngine.class);
public NotificationEngine(ForumsModule module)
{
try
{
this.forumsModule = module;
InitialContext ctx = new InitialContext();
tm = (TransactionManager)ctx.lookup("java:TransactionManager");
executor = new QueuedExecutor(new LinkedQueue());
userProfileModule = forumsModule.getUserProfileModule();
}
catch (NamingException e)
{
log.error("Cannot create notification interceptor", e);
}
}
public void stop()
{
executor.shutdownAfterProcessingCurrentTask();
executor = null;
forumsModule = null;
tm = null;
}
public void setFrom(String from)
{
this.from = from;
}
public TemplateLoader getMailTemplates()
{
return mailTemplates;
}
public void scheduleForNotification(Integer postId, int mode) {
Object responseObj = FacesContext.getCurrentInstance().getExternalContext().getResponse();
if (responseObj instanceof ActionResponse) {
ActionResponse response = (ActionResponse)responseObj;
response.setRenderParameter(Constants.p_notified_post_id, postId.toString());
response.setRenderParameter(Constants.p_notified_watch_type, Integer.toString(mode));
} else {
// TODO: IMPLEMENT NOTIFICATION FOR STANDALONE VERSION OF FORUMS.
log.warn("NOTIFICATION FOR STANDALONE HAS NOT BEEN YET IMPLEMENTED");
}
}
public void schedule(Integer postId, int mode, String absViewURL , String absReplyURL )
{
try
{
if (postId==null || mode == -1) {
log.warn("Request didn't have needed parameters.");
return;
}
// Getting ResourceBundle with current Locale
// Too bad for now we support notifications sent in the locale of the poster :-(
FacesContext ctx = FacesContext.getCurrentInstance();
UIViewRoot uiRoot = ctx.getViewRoot();
Locale locale = uiRoot.getLocale();
ClassLoader ldr = Thread.currentThread().getContextClassLoader();
ResourceBundle bundle = ResourceBundle.getBundle("ResourceJSF",locale,ldr);
// Getting Authorization realm
AuthorizationInterface realm = Authorization.getProvider();
// Create task
NotificationTask task = new NotificationTask(tm, absViewURL, absReplyURL ,
postId, mode, bundle , realm );
//Register at the end of the current tx to broadcast notifications
Transaction tx = tm.getTransaction();
tx.registerSynchronization(task);
}
catch (Exception e)
{
e.printStackTrace();
}
}
private String getFrom(Post post)
{
StringBuffer fromBuf = null;
try {
if ( (userProfileModule.getProperty(post.getPoster().getUser(),User.INFO_USER_NAME_GIVEN) != null)
&& (userProfileModule.getProperty(post.getPoster().getUser(),User.INFO_USER_NAME_FAMILY) != null))
{
fromBuf = new StringBuffer(userProfileModule.getProperty(post.getPoster().getUser(),User.INFO_USER_NAME_GIVEN)
+ " " + userProfileModule.getProperty(post.getPoster().getUser(),User.INFO_USER_NAME_FAMILY) + " <");
}
else
{
fromBuf = new StringBuffer(post.getPoster().getUser().getUserName() + " <");
}
fromBuf.append(from + ">");
} catch (IdentityException e) {
log.error(e);
}
return fromBuf.toString();
}
/**
* The notification task.
*/
class NotificationTask implements Transactions.Runnable, Synchronization
{
private final TransactionManager tm;
private final int mode;
private final Integer postId;
private final ResourceBundle bundle;
private final String viewURL;
private final String replyURL;
private final AuthorizationInterface realm;
NotificationTask(TransactionManager tm, String viewURL, String replyURL,
final Integer postId, int mode, ResourceBundle bundle , AuthorizationInterface realm)
{
this.tm = tm;
this.mode = mode;
this.postId = postId;
this.viewURL = viewURL;
this.replyURL = replyURL;
this.bundle = bundle;
this.realm = realm;
}
public Object run() throws Exception
{
try
{
Post post = forumsModule.findPostById(postId);
Topic topic = post.getTopic();
Forum forum = topic.getForum();
Category category = forum.getCategory();
Message message = post.getMessage();
String from = getFrom(post);
// Hold the notified users to avoid duplicated
Set notifieds = new HashSet();
// If this is not an anonymous post, put the user in the notified list
// he won't be notified of his own action
User poster = post.getPoster().getUser();
if (poster != null)
{
notifieds.add(poster.getId());
}
char[] chars = message.getText().toCharArray();
StringWriter out = new StringWriter();
ToTextRenderer renderer = new ToTextRenderer();
renderer.setWriter(out);
renderer.render(chars, 0, chars.length);
String forumEmbededArgsSubject = "[" + forum.getName() + "] - "
+ message.getSubject()
+ (mode == MODE_REPOST ? " (Repost)" : "");
String forumEmbededArgsText = out.toString() + "<br /><br />\n"
+ bundle.getString("EMAIL_VIEWORIGINAL") + " : "
+ "<a href=\""+viewURL.toString() + "\">" + viewURL.toString() + "</a>\n"
+ "<br /><br />\n"
+ bundle.getString("EMAIL_REPLY") + " : "
+ "<a href=\""+replyURL.toString() + "\">" + replyURL.toString() + "</a>"
+ "<br /><br />\n";
// For now it is just a copy from embeded mode maybe in future we will differentiate it.
String forumLinkedArgsSubject = forumEmbededArgsSubject;
String forumLinkedArgsText = bundle.getString("EMAIL_LINKED_MODE_INFO")+" <b>"+forum.getName()+"</b> <br /><br />\n"
+ bundle.getString("EMAIL_VIEWORIGINAL") + " : "
+ "<a href=\""+viewURL.toString() + "\">" + viewURL.toString() + "</a>\n"
+ "<br /><br />\n"
+ bundle.getString("EMAIL_REPLY") + " : "
+ "<a href=\""+replyURL.toString() + "\">" + replyURL.toString() + "</a>"
+ "<br /><br />\n";
// Notify the forum watchers
for (Iterator i = forum.getWatches().iterator(); i.hasNext();)
{
try
{
Watch watch = (Watch)i.next();
// If user don't want to be notified by e-mail then continue with next watch.
if (watch.getMode()==ForumsConstants.WATCH_MODE_NONE)
{
continue;
}
User watcher = watch.getPoster().getUser();
Object watcherId = watcher.getId();
if (!notifieds.contains(watcherId) && !watcherId.equals(PortalUtil.getUserNA().getId()))
{
boolean securityFlag = true;
// Creating security context for the user
JSFUIContext securityContext = new JSFUIContext(watcher,FacesContext.getCurrentInstance());
// Checking if user has privileges to read category
securityContext.setFragment("acl://readCategory");
securityContext.setContextData(new Object[]{category});
securityFlag = realm.hasAccess(securityContext) && securityFlag;
// Checking if user has privileges to read forum
securityContext.setFragment("acl://readForum");
securityContext.setContextData(new Object[]{forum});
securityFlag = realm.hasAccess(securityContext) && securityFlag;
if (securityFlag)
{
notifieds.add(watcherId);
String subject = null;
String text = null;
if (watch.getMode() == ForumsConstants.WATCH_MODE_EMBEDED)
{
subject = forumEmbededArgsSubject;
text = forumEmbededArgsText;
}
else if (watch.getMode()==ForumsConstants.WATCH_MODE_LINKED)
{
subject = forumLinkedArgsSubject;
text = forumLinkedArgsText;
}
notify ( watcher, from , subject, text );
}
else
{
// Not authorized anymore, we remove the watch
forumsModule.removeWatch(watch);
}
}
}
catch (Exception e)
{
log.error("Cannot send an email notification", e);
}
}
String topicEmbededArgsSubject = "[" + forum.getName() + "] - "
+ message.getSubject()
+ (mode == MODE_REPOST ? " (Repost)" : "");
String topicEmbededArgsText = out.toString() + "<br /><br />\n"
+ bundle.getString("EMAIL_VIEWORIGINAL") + " : "
+ "<a href=\""+viewURL.toString() + "\">" + viewURL.toString() + "</a>\n"
+ "<br /><br />\n"
+ bundle.getString("EMAIL_REPLY") + " : "
+ "<a href=\""+replyURL.toString() + "\">" + replyURL.toString() + "</a>"
+ "<br /><br />\n";
// For now it is just a copy from embeded mode maybe in future we will differentiate it.
String topicLinkedArgsSubject = topicEmbededArgsSubject;
String topicLinkedArgsText = bundle.getString("EMAIL_LINKED_MODE_INFO")+": <b>"+topic.getSubject()+"</b><br /><br />\n"
+ bundle.getString("EMAIL_VIEWORIGINAL") + " : "
+ "<a href=\""+viewURL.toString() + "\">" + viewURL.toString() + "</a>\n"
+ "<br /><br />\n"
+ bundle.getString("EMAIL_REPLY") + " : "
+ "<a href=\""+replyURL.toString() + "\">" + replyURL.toString() + "</a>"
+ "<br /><br />\n";
// Notify the topic watchers
if (mode == MODE_REPLY)
{
// Notify the reply watchers
for (Iterator i = topic.getWatches().iterator(); i.hasNext();)
{
try
{
TopicWatch watch = (TopicWatch)i.next();
// If user don't want to be notified by e-mail then continue with next watch.
if (watch.getMode()==ForumsConstants.WATCH_MODE_NONE)
{
continue;
}
User watcher = watch.getPoster().getUser();
Object watcherId = watcher.getId();
if (!notifieds.contains(watcherId) && !watcherId.equals(PortalUtil.getUserNA().getId()))
{
boolean securityFlag = true;
// Creating security context for the user
JSFUIContext securityContext = new JSFUIContext(watcher,FacesContext.getCurrentInstance());
// Checking if user has privileges to read category
securityContext.setFragment("acl://readCategory");
securityContext.setContextData(new Object[]{category});
securityFlag = realm.hasAccess(securityContext) && securityFlag;
// Checking if user has privileges to read forum
securityContext.setFragment("acl://readForum");
securityContext.setContextData(new Object[]{forum});
securityFlag = realm.hasAccess(securityContext) && securityFlag;
if (securityFlag)
{
// Authorized
notifieds.add(watcherId);
String subject = null;
String text = null;
if (watch.getMode() == ForumsConstants.WATCH_MODE_EMBEDED)
{
subject = topicEmbededArgsSubject;
text = topicEmbededArgsText;
}
else if (watch.getMode()==ForumsConstants.WATCH_MODE_LINKED)
{
subject = topicLinkedArgsSubject;
text = topicLinkedArgsText;
}
notify( watcher, from , subject , text );
}
else
{
// Not authorized anymore, we remove the watch
forumsModule.removeWatch(watch);
}
}
}
catch (Exception e)
{
log.error("Cannot send email notification", e);
}
}
}
}
catch (IllegalArgumentException e)
{
log.error("", e);
}
return null;
}
public void beforeCompletion()
{
}
private void notify (User watcher, String from , String subject, String text) {
Session session = null;
try {
session = (Session) PortableRemoteObject
.narrow(new InitialContext().lookup("java:Mail"),
Session.class);
try {
StringBuffer buffer = null;
Address[] to = null;
MimeMessage m = new MimeMessage(session);
String email = userProfileModule.getProperty(watcher,User.INFO_USER_EMAIL_REAL).toString();
if ( email != null ) {
m.setFrom(new InternetAddress(from));
to = new InternetAddress[] { new InternetAddress(
email) };
m.setRecipients(RecipientType.TO, to);
m.setSubject(subject);
m.setSentDate(new Date());
buffer = new StringBuffer();
buffer.append(text);
buffer.append(bundle.getString("EMAIL_FOOTER_MESSAGE"));
m.setContent(buffer.toString(),
"text/html; charset=\"UTF-8\"");
Transport.send(m);
}
} catch (javax.mail.MessagingException e) {
log.error(e);
} catch (IdentityException e2) {
log.error(e2);
}
} catch (javax.naming.NamingException e) {
log.error(e);
}
}
public void afterCompletion(int status)
{
// When transaction succesfully commits broadcast the notification
if (status == Status.STATUS_COMMITTED)
{
try
{
// Do it asynch
executor.execute(new Runnable()
{
public void run()
{
try
{
// Wrap with a tx
Transactions.required(tm, NotificationTask.this);
}
catch (Exception e)
{
log.error("Cannot broadcast nofication for post id", e);
}
}
});
}
catch (InterruptedException ignored)
{
}
}
}
}
}
|