/*
* Adito
*
* Copyright (C) 2003-2006 3SP LTD. All Rights Reserved
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package com.adito.notification;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import javax.management.relation.RoleNotFoundException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.adito.core.CoreAttributeConstants;
import com.adito.core.CoreEvent;
import com.adito.core.CoreEventConstants;
import com.adito.core.CoreServlet;
import com.adito.core.UserDatabaseManager;
import com.adito.policyframework.Policy;
import com.adito.policyframework.PolicyDatabase;
import com.adito.policyframework.PolicyDatabaseFactory;
import com.adito.policyframework.Principal;
import com.adito.realms.Realm;
import com.adito.security.LogonControllerFactory;
import com.adito.security.Role;
import com.adito.security.User;
import com.adito.security.UserDatabase;
import com.adito.security.UserDatabaseException;
public class Notifier {
final static Log log = LogFactory.getLog(Notifier.class);
private List<MessageWrapper> messages;
private boolean started;
private File queueDirectory;
private long messageId;
private MessageConsumer messageConsumer;
private boolean stop;
private List<MessageSink> sinks;
private HashMap<String,Boolean> sinkEnabled;
public Notifier(File queueDirectory) throws IOException {
messages = new ArrayList<MessageWrapper>();
this.queueDirectory = queueDirectory;
sinks = new ArrayList<MessageSink>();
sinkEnabled = new HashMap<String,Boolean>();
loadFromDisk();
}
public List<MessageSink> getSinks() {
return sinks;
}
public Collection<MessageSink> getEnabledSinks() {
Collection<MessageSink> enabledSinks = new ArrayList<MessageSink>();
for (MessageSink sink : sinks) {
if(isEnabled(sink.getName())) {
enabledSinks.add(sink);
}
}
return enabledSinks;
}
public void addSink(MessageSink sink, boolean enabled) {
if(enabled && started) {
try {
sink.start(this);
} catch(Exception ex) {
log.error("Failed to start " + sink.getName() + " message sink", ex);
return;
}
}
sinks.add(sink);
sinkEnabled.put(sink.getName(), Boolean.valueOf(enabled));
}
public void removeSink(MessageSink sink) {
sinks.remove(sink);
sinkEnabled.remove(sink.getName());
}
public boolean isEnabled(String sinkName) {
return Boolean.TRUE.equals(sinkEnabled.get(sinkName));
}
public void start() throws IllegalStateException {
if (started) {
throw new IllegalStateException("Already started.");
}
messageConsumer = new MessageConsumer();
if (sinks.size() == 0) {
throw new IllegalStateException(
"At least one message sink must have been registered for the notfication queue to be started.");
}
for (Iterator i = sinks.iterator(); i.hasNext();) {
MessageSink sink = (MessageSink) i.next();
try {
if (log.isDebugEnabled())
log.debug("Starting message sink " + sink.getName());
sink.start(this);
} catch (Exception e) {
log.error("Failed to start sink " + sink.getName() + ".", e);
}
}
started = true;
messageConsumer.start();
if (log.isInfoEnabled())
log.info("Notifier started");
}
void loadFromDisk() throws IOException {
File[] f = queueDirectory.listFiles(new FileFilter() {
public boolean accept(File f) {
return f.getName().endsWith(".msg");
}
});
// TODO better error handling in parsing of message files. Report on
// non-existant / unreadable directory
if (f == null) {
throw new IOException("Could not list queue directory " + queueDirectory.getAbsolutePath());
}
for (int i = 0; i < f.length; i++) {
FileInputStream fin = new FileInputStream(f[i]);
try {
DataInputStream din = new DataInputStream(fin);
long id = din.readLong();
String sinkName = din.readUTF();
messageId = Math.max(id, messageId);
boolean urgent = din.readBoolean();
String subject = din.readUTF();
List<Recipient> recipientList = new ArrayList<Recipient>();
while (true) {
int recipientType = din.readInt();
if (recipientType == Recipient.EOF) {
break;
} else {
String recipientAlias = din.readUTF();
String realmName = din.readUTF();
Recipient recipient = new Recipient(recipientType, recipientAlias, realmName);
recipientList.add(recipient);
}
}
Properties parameters = new Properties();
while (true) {
int parameterType = din.readInt();
if (parameterType < 1) {
break;
} else {
String key = din.readUTF();
String val = din.readUTF();
parameters.setProperty(key, val);
}
}
String content = din.readUTF();
String lastMessage = din.readUTF();
Message msg = new Message(subject, content, urgent);
msg.setId(id);
msg.setRecipients(recipientList);
msg.setSinkName(sinkName);
msg.setLastMessage(lastMessage);
queue(msg);
} finally {
fin.close();
}
}
}
public void stop() throws IllegalStateException {
if (!started) {
throw new IllegalStateException("Notifier is not started.");
}
stop = true;
queueNotify();
started = false;
if (log.isInfoEnabled())
log.info("Waiting for up to 120 seconds message consumer to stop");
try {
messageConsumer.join(120000);
} catch (InterruptedException ie) {
}
for (Iterator i = sinks.iterator(); i.hasNext();) {
MessageSink sink = (MessageSink) i.next();
try {
sink.stop();
} catch (Exception e) {
log.error("Failed to stop sink " + sink.getName() + ".", e);
}
}
if (log.isInfoEnabled())
log.info("Notifier stopped");
}
public boolean isStarted() {
return started;
}
public void clearAllMessages() {
synchronized (messages) {
try {
messages.clear();
File[] f = queueDirectory.listFiles(new FileFilter() {
public boolean accept(File f) {
return f.getName().endsWith(".msg");
}
});
if (f != null) {
for (int i = 0; i < f.length; i++) {
f[i].delete();
}
}
CoreServlet.getServlet().fireCoreEvent(new CoreEvent(this, CoreEventConstants.MESSAGE_QUEUE_CLEARED, null, null, CoreEvent.STATE_SUCCESSFUL));
} catch (Exception e) {
log.error("Failed to clear messages from queue", e);
CoreServlet.getServlet().fireCoreEvent(new CoreEvent(this, CoreEventConstants.MESSAGE_QUEUE_CLEARED, null, null, CoreEvent.STATE_UNSUCCESSFUL));
}
}
}
public void sendToAll(Message message) {
sendToSink("*", message);
}
public void sendToAllExcept(Message message, String except) {
sendToSink("!" + except, message);
}
public void sendToFirst(Message message) {
sendToSink("^", message);
}
public void sendToSink(String sinkName, Message message) throws IllegalArgumentException {
messageId++;
message.setSinkName(sinkName);
message.setId(messageId);
if (log.isDebugEnabled())
log.debug("Sending message " + message.getId() + " '" + message.getSubject() + "' to sink '" + sinkName + "'");
queue(message);
try {
write(message);
} catch (IOException ioe) {
}
}
public void setEnabled(String sinkName, boolean enabled) {
sinkEnabled.put(sinkName, Boolean.valueOf(enabled));
if (enabled) {
queueNotify();
}
}
public MessageWrapper getMessage(long messageId) {
for (MessageWrapper wrapper : getMessages()) {
if(wrapper.getMessage().getId() == messageId)
return wrapper;
}
return null;
}
public List<MessageWrapper> getMessages() {
return messages;
}
public int send(Collection<Recipient> recipients, MessageSender sender) throws Exception {
for (Recipient recipient : recipients) {
if (recipient.getRecipientType() == Recipient.USER) {
sender.sendMessage(recipient.getRecipientAlias(), recipient);
} else if (recipient.getRecipientType() == Recipient.POLICY) {
sendByPolicy(sender, recipient);
} else if (recipient.getRecipientType() == Recipient.ROLE) {
sendByRole(sender, recipient);
} else if (recipient.getRecipientType() == Recipient.ADMINS) {
sendByAdmin(sender, recipient);
}
}
return sender.getSentMessageCount();
}
private void sendByPolicy(MessageSender sender, Recipient recipient) throws Exception, UserDatabaseException {
UserDatabase userDatabase = UserDatabaseManager.getInstance().getUserDatabase(recipient.getRealmName());
PolicyDatabase policyDatabase = PolicyDatabaseFactory.getInstance();
Realm realm = userDatabase.getRealm();
Policy policy = policyDatabase.getPolicyByName(recipient.getRecipientAlias(), realm.getResourceId());
int everyonePolicyId = policyDatabase.getEveryonePolicyIDForRealm(realm);
if (everyonePolicyId == policy.getResourceId()) {
sendToPrincipals(sender, userDatabase, userDatabase.allRoles());
sendToPrincipals(sender, userDatabase, userDatabase.allUsers());
} else {
List<Principal> principals = policyDatabase.getPrincipalsGrantedPolicy(policy, realm);
sendToPrincipals(sender, userDatabase, principals);
}
}
private void sendToPrincipals(MessageSender sender, UserDatabase userDatabase, Iterable<? extends Principal> principals)
throws UserDatabaseException, Exception {
for (Principal principal : principals) {
if (principal instanceof Role) {
sendByRole(sender, userDatabase, (Role) principal);
} else {
Realm realm = principal.getRealm();
Recipient newRecipient = new Recipient(Recipient.USER, principal.getPrincipalName(), realm.getResourceName());
sender.sendMessage(principal.getPrincipalName(), newRecipient);
}
}
}
private void sendByRole(MessageSender sender, Recipient recipient) throws Exception, RoleNotFoundException, UserDatabaseException {
UserDatabase userDatabase = UserDatabaseManager.getInstance().getUserDatabase(recipient.getRealmName());
Role role = userDatabase.getRole(recipient.getRecipientAlias());
sendByRole(sender, userDatabase, role);
}
private void sendByRole(MessageSender sender, UserDatabase userDatabase, Role role) throws UserDatabaseException, Exception {
User[] usersInRole = userDatabase.getUsersInRole(role);
for (User user : usersInRole) {
Recipient newRecipient = new Recipient(Recipient.USER, user.getPrincipalName(), user.getRealm().getResourceName());
sender.sendMessage(user.getPrincipalName(), newRecipient);
}
}
private void sendByAdmin(MessageSender sender, Recipient recipient) throws Exception, UserDatabaseException {
UserDatabase userDatabase = UserDatabaseManager.getInstance().getUserDatabase(recipient.getRealmName());
for (User user : userDatabase.allUsers()) {
if (LogonControllerFactory.getInstance().isAdministrator(user)) {
Recipient newRecipient = new Recipient(Recipient.USER, user.getPrincipalName(), user.getRealm().getResourceName());
sender.sendMessage(user.getPrincipalName(), newRecipient);
}
}
}
MessageSink getSink(String sinkName) {
MessageSink s;
for (Iterator i = sinks.iterator(); i.hasNext();) {
s = (MessageSink) i.next();
if (s.getName().equals(sinkName)) {
return s;
}
}
return null;
}
void write(Message message) throws IOException {
if (log.isDebugEnabled())
log.debug("Writing message " + message.getId() + " '" + message.getSubject() + "' to disk");
FileOutputStream fout = new FileOutputStream(new File(queueDirectory, String.valueOf(message.getId()) + ".msg"));
try {
DataOutputStream dout = new DataOutputStream(fout);
dout.writeLong(message.getId());
dout.writeUTF(message.getSinkName());
dout.writeBoolean(message.isUrgent());
dout.writeUTF(message.getSubject());
for (Iterator i = message.getRecipients().iterator(); i.hasNext();) {
Recipient r = (Recipient) i.next();
dout.writeInt(r.getRecipientType());
dout.writeUTF(r.getRecipientAlias() == null ? "" : r.getRecipientAlias());
dout.writeUTF(r.getRealmName() == null ? "" : r.getRealmName());
}
dout.writeInt(0);
for (Iterator i = message.getParameterNames(); i.hasNext();) {
String key = (String) i.next();
dout.writeInt(1);
dout.writeUTF(key);
dout.writeUTF(message.getParameter(key));
}
dout.writeInt(0);
dout.writeUTF(message.getContent());
dout.writeUTF(message.getLastMessage());
} finally {
fout.close();
}
}
void queue(Message message) {
if (log.isDebugEnabled())
log.debug("Queueing message " + message.getId() + " '" + message.getSubject() + "'");
MessageWrapper wrapper = new MessageWrapper(message);
if (message.isUrgent()) {
messages.add(0, wrapper);
fireQueuedEvent(message);
queueNotify();
} else {
messages.add(wrapper);
fireQueuedEvent(message);
}
}
void fireQueuedEvent(Message message) {
fireMessageEvent(message, new CoreEvent(this, CoreEventConstants.MESSAGE_QUEUED, message, null, CoreEvent.STATE_SUCCESSFUL));
}
void fireSentEvent(Message message, int state) {
fireMessageEvent(message, new CoreEvent(this, CoreEventConstants.MESSAGE_SENT, message, null, state));
}
void fireMessageEvent(Message message, CoreEvent evt) {
List l = message.getRecipients();
StringBuffer userR = new StringBuffer();
StringBuffer roleR = new StringBuffer();
StringBuffer policyR = new StringBuffer();
for (Iterator i = l.iterator(); i.hasNext();) {
Recipient r = (Recipient) i.next();
StringBuffer rb = userR;
if (r.getRecipientType() == Recipient.ROLE) {
rb = roleR;
} else if (r.getRecipientType() == Recipient.POLICY) {
rb = policyR;
}
if (rb.length() > 0) {
rb.append(",");
}
rb.append(r.getRecipientAlias());
}
CoreServlet.getServlet().fireCoreEvent(
evt.addAttribute(CoreAttributeConstants.EVENT_ATTR_MESSAGE_ID, String.valueOf(message.getId()))
.addAttribute(CoreAttributeConstants.EVENT_ATTR_MESSAGE_URGENT,
String.valueOf(message.isUrgent())).addAttribute(
CoreAttributeConstants.EVENT_ATTR_MESSAGE_SUBJECT,
String.valueOf(message.getSubject()))
.addAttribute(CoreAttributeConstants.EVENT_ATTR_MESSAGE_ROLE_RECIPIENTS, roleR.toString())
.addAttribute(CoreAttributeConstants.EVENT_ATTR_MESSAGE_POLICY_RECIPIENTS,
policyR.toString()));
}
void queueNotify() {
if (log.isDebugEnabled())
log.debug("Notify queue");
synchronized (messages) {
messages.notifyAll();
}
if (log.isDebugEnabled())
log.debug("Queue notified");
}
public boolean doSend(String sinkName, Message message) {
message.setSinkName(sinkName);
return doSend(message);
}
boolean doSend(Message message) {
if(log.isDebugEnabled())
log.debug("Sending message with subject of " + message.getSubject() + " urgent = " + message.isUrgent());
if (log.isDebugEnabled()) {
for (Iterator i = message.getRecipients().iterator(); i.hasNext();) {
Recipient r = (Recipient) i.next();
log.debug(" " + r.getRecipientType() + "/" + r.getRecipientAlias());
}
log.debug("Content = " + message.getContent());
log.debug("Sink name = " + message.getSinkName());
}
boolean sent = false;
if (message.getSinkName().equals("*")) {
for (Iterator i = sinks.iterator(); i.hasNext();) {
MessageSink sink = (MessageSink) i.next();
if (Boolean.TRUE == sinkEnabled.get(sink.getName())) {
try {
if (sink.send(message)) {
sent = true;
}
} catch (Exception e) {
log.error("Failed to send message " + message.getId() + ".", e);
}
}
}
} else if (message.getSinkName().startsWith("!")) {
String[] except = message.getSinkName().substring(1).split(",");
for (Iterator i = sinks.iterator(); i.hasNext();) {
MessageSink sink = (MessageSink) i.next();
boolean found = false;
for (int j = 0; j < except.length && !found; j++) {
if (sink.getName().equals(except[j])) {
found = true;
}
}
if (!found && Boolean.TRUE == sinkEnabled.get(sink.getName())) {
try {
if (sink.send(message)) {
sent = true;
}
} catch (Exception e) {
log.error("Failed to send message " + message.getId() + ".", e);
}
}
}
} else if (message.getSinkName().equals("^")) {
for (Iterator i = sinks.iterator(); !sent && i.hasNext();) {
MessageSink sink = (MessageSink) i.next();
if (Boolean.TRUE == sinkEnabled.get(sink.getName())) {
try {
if (sink.send(message)) {
sent = true;
}
} catch (Exception e) {
log.error("Failed to send message " + message.getId() + ".", e);
}
}
}
} else {
MessageSink s = getSink(message.getSinkName());
if (s == null) {
log.error("No message sink named " + message.getSinkName());
} else {
if (Boolean.TRUE == sinkEnabled.get(s.getName())) {
try {
sent = s.send(message);
} catch (Exception e) {
message.setLastMessage(e.getMessage());
log.error("Failed to send message " + message.getId() + ".", e);
;
}
}
}
}
if (!sent) {
log.error("No message sink sent message " + message.getId());
fireSentEvent(message, CoreEvent.STATE_UNSUCCESSFUL);
}
else {
fireSentEvent(message, CoreEvent.STATE_SUCCESSFUL);
}
return sent;
}
class MessageConsumer extends Thread {
MessageConsumer() {
super("Notification Message Consumer");
}
public void run() {
stop = false;
MessageWrapper msg = null;
int valid = -1;
boolean waitOnce = false;
while (!stop) {
synchronized (messages) {
while ((waitOnce || messages.size() == 0) && !stop) {
try {
messages.wait(30000);
} catch (InterruptedException ie) {
log.error("MessageConsumer interrupted.", ie);
}
waitOnce = false;
}
if (!stop) {
if (log.isDebugEnabled())
log.debug("Checking message queue");
int i = 0;
valid = -1;
while (i < messages.size()) {
msg = (MessageWrapper) messages.get(i);
if (log.isDebugEnabled())
log.debug("Checking if message " + msg.getMessage().getId() + " is valid");
// If the message has been attempted before, check
// whether its ready for retry
if (msg.attempt == 0 || (msg.attempt > 0 && (msg.time.getTime() + 60000) < System.currentTimeMillis())) {
valid = i;
break;
} else {
i++;
}
}
if (valid != -1) {
messages.remove(valid);
}
}
}
if (!stop) {
boolean sent = false;
if (valid != -1) {
sent = doSend(msg.message);
if (!sent) {
msg.attempt++;
msg.time = new Date();
try {
Thread.sleep(1);
} catch (InterruptedException ie) {
}
log.error("Failed to send message. " + msg.getMessage().getLastMessage());
messages.add(msg);
} else {
new File(queueDirectory, msg.message.getId() + ".msg").delete();
}
} else {
waitOnce = true;
}
}
}
}
}
public class MessageWrapper {
private final Message message;
private Date time;
private int attempt;
private MessageWrapper(Message message) {
this.message = message;
this.time = new Date();
}
public Message getMessage() {
return message;
}
public int getAttempt() {
return attempt;
}
public Date getTime() {
return time;
}
public String getFormattedTime() {
DateFormat sdf = SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT);
return sdf.format(getTime());
}
public String getUserRecipients() {
return getRecipients(Recipient.USER);
}
public String getRoleRecipients() {
return getRecipients(Recipient.ROLE);
}
public String getPolicyRecipients() {
return getRecipients(Recipient.POLICY);
}
private String getRecipients(int type) {
List<String> recipients = new ArrayList<String>();
for (Recipient recipient : message.getRecipients()) {
if(recipient.getRecipientType() == type) {
recipients.add(recipient.getRecipientAlias());
}
}
Collections.sort(recipients);
StringBuffer buffer = new StringBuffer();
for (String value : recipients) {
buffer.append(value).append(", ");
}
return (buffer.length() == 0 ) ? "" : buffer.substring(0, buffer.length() - 2);
}
}
}