com.untzuntz.ustack.main.UNotificationSvc Maven / Gradle / Ivy
package com.untzuntz.ustack.main;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.net.URI;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javapns.Push;
import javapns.communication.exceptions.CommunicationException;
import javapns.communication.exceptions.KeystoreException;
import javapns.devices.Device;
import javapns.notification.PushNotificationPayload;
import javapns.notification.PushedNotification;
import javapns.notification.ResponsePacket;
import javapns.notification.transmission.PushQueue;
import javax.mail.internet.AddressException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.log4j.Logger;
import com.mongodb.BasicDBList;
import com.mongodb.DBObject;
import com.twilio.sdk.TwilioRestClient;
import com.twilio.sdk.TwilioRestResponse;
import com.untzuntz.ustack.data.NotificationInst;
import com.untzuntz.ustack.data.NotificationTemplate;
import com.untzuntz.ustack.data.PushQueueInstance;
import com.untzuntz.ustack.util.BasicUtils;
public class UNotificationSvc {
protected static Logger logger = Logger.getLogger(UNotificationSvc.class);
private static final Hashtable iosPushQueues = new Hashtable();
private boolean testMode;
private Hashtable supportingData;
private Hashtable attachments;
private List skips;
public static void main(String[] args) throws Exception {
if (args.length < 5)
{
System.err.println("Usage: com.untzuntz.ustack.main.UNotificationSvc -addpushqueue [appname] [queuename] [filename] [password]");
return;
}
if (args[0].equalsIgnoreCase("-addpushqueue"))
{
UOpts.setAppName(args[1]);
PushQueueInstance pushQ = PushQueueInstance.createKeyStore(args[2], new FileInputStream(args[3]), args[4]);
pushQ.save("System");
}
}
public UNotificationSvc()
{
this.attachments = new Hashtable();
this.supportingData = new Hashtable();
this.skips = new Vector();
}
public static KeyStore loadKeystore(InputStream keystoreStream, char[] password) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
KeyStore keyStore;
keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(keystoreStream, password);
return keyStore;
}
public static PushQueue getIOSPushQueue(String name) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeystoreException
{
if (name == null)
return null;
PushQueue queue = iosPushQueues.get(name);
if (queue == null)
{
PushQueueInstance ksd = PushQueueInstance.getByName(name);
if (ksd != null)
{
String password = ksd.getPassword();
logger.info("Loading Push Queue [" + name + "]");
queue = Push.queue(loadKeystore(new ByteArrayInputStream(ksd.getBinaryData()), password.toCharArray()), password, ksd.isProduction(), ksd.getThreads());
queue.start();
iosPushQueues.put(name, queue);
}
else
logger.error("Failed to find ios push queue config => " + name);
}
else
logger.info("Found Push Queue [" + name + "]");
return queue;
}
/**
* A skip will tell the notification to run all the notifications except those that match in the parameters provided.
*
* Example:
*
* A site has 3 users subscribed to it's new.document notification event
*
* User #1 of the site initiated the new.document so he/she doesn't need to be alerted
*
* To do this, add user #1 to the skip list.
*
*
* SiteId's are also supported
*
* @param skip
*/
public void addSkip(String skip)
{
if (skip == null)
return;
skips.add(skip);
}
/**
* Sets an attachment. The provided name is used to name the file
* @param name
* @param location
* @return
*/
public UNotificationSvc setAttachment(String name, File location) {
if (name == null)
return this;
if (location == null)
attachments.remove(name);
else
attachments.put(name, location);
return this;
}
/** Set objects for use in the template */
public UNotificationSvc setData(String name, DBObject data)
{
if (name == null)
return this;
if (data == null)
supportingData.remove(name);
else
supportingData.put(name, data);
return this;
}
public void setTestMode(boolean mode)
{
testMode = mode;
}
/** Execute Notifications */
public int notify(String eventName, DBObject search)
{
int alertsSent = 0;
NotificationTemplate template = NotificationTemplate.getNotificationTemplate(eventName);
if (template == null)
return -1;
List notiList = NotificationInst.getNotifications(eventName, search);
for (NotificationInst notif : notiList)
{
BasicDBList recvTypes = notif.getTypeList();
for (int i = 0; i < recvTypes.size(); i++)
{
DBObject type = (DBObject)recvTypes.get(i);
// check if the template has the same type (or name)
DBObject templateTypeCheck = template.getType( (String)type.get("name") );
if (templateTypeCheck != null)
{
// send the actual notification
if (sendNotification(notif, template, type))
alertsSent++;
}
else
logger.warn(notif.getNotificationId() + " has template type '" + type.get("name") + "' but the template (" + eventName + ") does not.");
}
}
return alertsSent;
}
/**
* Actually send the notification - by known type then by an interface
* @param template
*/
private boolean sendNotification(NotificationInst notif, NotificationTemplate template, DBObject endpointConfig)
{
String type = (String)endpointConfig.get("name");
for (String skip : skips)
{
if (skip.equalsIgnoreCase((String)endpointConfig.get("userName")))
{
logger.info("Skipping Notification for user '" + skip + "' <- Configured");
return false;
}
else if (skip.equalsIgnoreCase((String)endpointConfig.get("siteId")))
{
logger.info("Skipping Notification for site id '" + skip + "' <- Configured");
return false;
}
}
if ("email".equalsIgnoreCase(type))
return sendEmail(notif, template, endpointConfig);
else if ("sms".equalsIgnoreCase(type))
return sendSMS(notif, template, endpointConfig);
else if ("ios-push".equalsIgnoreCase(type))
return sendIOSPush(notif, template, endpointConfig);
else if ("facebook".equalsIgnoreCase(type))
return sendFacebook(notif, template, endpointConfig);
else
{
// TODO: Implement custom interface
logger.warn("Unknown template type '" + type + "'");
}
return false;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public boolean sendFacebook(NotificationInst notif, NotificationTemplate template, DBObject endpointConfig)
{
if (notif != null && notif.get("invalid") != null)
{
logger.info("Skipping Facebook Delivery for notification id [" + notif.getNotificationId() + "] => Reason: " + notif.get("invalid"));
return false;
}
DBObject templ = template.getType("facebook");
if (templ == null)
{
if (notif == null)
return false;
logger.info("Unknown template type [facebook] for notification id [" + notif.getNotificationId() + "]");
return false;
}
DefaultHttpClient client = null;
try {
String alert = processTemplate( (String)templ.get("templateText"), notif );
String destination = (String)endpointConfig.get("destination");
if (destination == null && templ.get("postTo") != null)
destination = processTemplate( (String)templ.get("postTo"), notif);
if (destination == null)
destination = "me";
String token = (String)endpointConfig.get("token");
// message=Play%20me%20in%20TWF%20now!&name=Join%20Now&caption=TWF&description=TWF%20Login&link=http://google.com?uid=1234
List qparams = new ArrayList();
qparams.add(new BasicNameValuePair("message", alert));
qparams.add(new BasicNameValuePair("name", (String)templ.get("linkName") ));
qparams.add(new BasicNameValuePair("caption", (String)templ.get("caption") ));
qparams.add(new BasicNameValuePair("description", (String)templ.get("description") ));
qparams.add(new BasicNameValuePair("link", processTemplate((String)templ.get("link"), notif) ));
qparams.add(new BasicNameValuePair("picture", (String)templ.get("picture") ));
qparams.add(new BasicNameValuePair("access_token", token));
URI uri = URIUtils.createURI("https", "graph.facebook.com", 443, "/" + destination + "/feed", null, null);
logger.info("URL: " + uri);
client = new DefaultHttpClient();
HttpPost post = new HttpPost(uri);
post.setEntity(new UrlEncodedFormEntity(qparams)); // setup parameters
HttpResponse response = client.execute(post);
qparams.clear();
if (response.getStatusLine().getStatusCode() == 200)
return true;
HttpEntity e = response.getEntity();
Writer writer = BasicUtils.getResponseString(e);
if (writer.toString().indexOf("Session has expired at unix time") > -1)
{
}
else
{
logger.error("Response Specifics: " + writer.toString());
if (notif != null)
notif.disable("Invalid response from Facebook API");
}
} catch (Exception er) {
logger.warn("Failed to deliver notification to Facebook", er);
close(client);
return false;
} finally {
close(client);
}
return true;
}
private void close(DefaultHttpClient client)
{
if (client == null)
return;
try {
client.getConnectionManager().shutdown();
} catch (Exception e) {}
}
public boolean sendIOSPush(NotificationInst notif, NotificationTemplate template, DBObject endpointConfig)
{
if (notif.get("invalid") != null)
{
logger.info("Skipping iOS push for notification id [" + notif.getNotificationId() + "] => Reason: " + notif.get("invalid"));
return false;
}
DBObject templ = template.getType("ios-push");
if (templ == null)
{
logger.info("Unknown template type [ios-push] for notification id [" + notif.getNotificationId() + "]");
return false;
}
String alert = processTemplate( (String)templ.get("templateText"), notif );
String iosPushQueueName = (String)templ.get("iosPushQueueName");
String destination = (String)endpointConfig.get("destination");
return sendIOSPush(destination, alert, iosPushQueueName, notif);
}
public boolean sendIOSPush(String destination, String alert, String iosPushQueueName, NotificationInst notif)
{
if (iosPushQueueName == null)
{
logger.error("Invalid iosPushQueueName value - null!");
return false;
}
if (destination.startsWith("<"))
{
destination = destination.substring(1, destination.length() - 1);
destination = destination.replaceAll(" ", "");
logger.info("Adjust Device ID: " + destination);
}
boolean ret = false;
try {
PushNotificationPayload payload = PushNotificationPayload.complex();
payload.addAlert(alert);
PushQueue queue = getIOSPushQueue(iosPushQueueName);
if (queue != null)
{
logger.info("Adding payload to queue [" + iosPushQueueName + "]");
queue.add(payload, destination);
}
// List notifications = Push.alert(alert, "pushtest-push.p12", "Jedman123!", false, new String[] { destination });
//
// for (PushedNotification notification : notifications)
// {
// }
} catch (KeystoreException e) {
/* A critical problem occurred while trying to use your keystore */
logger.error("Error while communicating with iOS Push [CERT]", e);
} catch (Exception e) {
logger.warn("Error while with iOS Push [OTHER]", e);
}
return ret;
}
@SuppressWarnings("unused")
private void handlePushedNotification(PushedNotification notification, NotificationInst notif)
{
if (!notification.isSuccessful())
{
String reason = null;
String invalidToken = notification.getDevice().getToken();
/* Find out more about what the problem was */
Exception theProblem = notification.getException();
reason = theProblem.getMessage();
logger.warn("Failed to push message to iOS device[" + invalidToken + "]", theProblem);
/* If the problem was an error-response packet returned by Apple, get it */
ResponsePacket theErrorResponse = notification.getResponse();
if (theErrorResponse != null) {
reason = theErrorResponse.getMessage();
logger.warn("Failed to push message to iOS device[" + invalidToken + "] => " + reason);
}
if (notif != null)
notif.disable(reason);
}
}
/** Send an Email */
public boolean sendEmail(NotificationInst notif, NotificationTemplate template, DBObject endpointConfig)
{
DBObject typeData = template.getType("email");
// String emailToName = (String)endpointConfig.get("destinationName");
String emailToAddr = (String)endpointConfig.get("destination");
String emailFromName = processTemplate( (String)typeData.get("fromName"), notif );
String emailFromAddr = processTemplate( (String)typeData.get("fromAddress"), notif );
String subject = processTemplate( (String)typeData.get("subject"), notif );
String emailBody = processTemplate( (String)typeData.get("templateText"), notif );
String htmlEmailBody = null;
if (typeData.get("htmlTemplateText") != null)
htmlEmailBody = processTemplate( (String)typeData.get("htmlTemplateText"), notif );
if (emailToAddr == null)
{
logger.warn("Invalid Email Destination (null): " + notif);
return false;
}
if (testMode)
logger.info("TESTMODE ==> From: " + emailFromAddr + " | To: " + emailToAddr + " | Subj: " + subject + " ==> " + emailBody);
else
{
try {
Emailer.postMail(emailToAddr, emailFromAddr, emailFromName, subject, emailBody, htmlEmailBody, attachments);
} catch (AddressException err) {
logger.error("Invalid Address : " + err);
return false;
}
}
return true;
}
/**
* Clears out spaces and dashes from a phone number.
* @param toPhone
* @return
*/
public static String trimPhoneNumber(String toPhone) {
if (toPhone == null)
return null;
toPhone = toPhone.replaceAll(" ", "");
toPhone = toPhone.replaceAll("-", "");
return toPhone;
}
/** Send an SMS */
public boolean sendSMS(NotificationInst notif, NotificationTemplate template, DBObject endpointConfig)
{
String smsMsg = processTemplate( (String)template.getType("sms").get("templateText"), notif );
String destination = (String)endpointConfig.get("destination");
String toPhone = destination;
try {
// open secure connection
TwilioRestClient client = new TwilioRestClient(UOpts.getProperty("EvenFlow.Twillio.APIKey"), UOpts.getProperty("EvenFlow.Twillio.APIAuthToken"), null);
toPhone = trimPhoneNumber(toPhone);
if (toPhone.length() == 10 && !toPhone.startsWith("1"))
toPhone = "1" + toPhone;
if (!toPhone.startsWith("+"))
toPhone = "+" + toPhone;
logger.info("Sending SMS [" + UOpts.getProperty("EvenFlow.Twillio.SMSPhoneNumber") + " => " + toPhone + "] // " + smsMsg + " via Twilio");
Map params = new HashMap();
params.put("From", UOpts.getProperty("EvenFlow.Twillio.SMSPhoneNumber"));
params.put("To", toPhone);
params.put("Body", smsMsg);
TwilioRestResponse response = client.request("/2010-04-01/Accounts/" + client.getAccountSid() + "/SMS/Messages", "POST", params);
if (response == null)
throw new Exception("Failed to send SMS via Twillio [To: " + toPhone + "]");
else if (response.isError())
throw new Exception("Failed to send SMS via Twillio [To: " + toPhone + "] -> " + response.getHttpStatus() + " // " + response.getResponseText());
logger.info("Twillio Response: " + response.getResponseText());
} catch (Exception er) {
// queue message?
logger.error("Failed to deliver SMS to [To: " + toPhone + "/" + destination + "]", er);
}
return true;
}
/** Process Template Text */
private String processTemplate(String text, DBObject other)
{
while (text != null && text.indexOf("${") > -1)
{
int start = text.indexOf("${");
int end = text.indexOf("}", start + 1);
String variable = text.substring(start + 2, end);
String varValue = "";
if (variable.indexOf(".") > -1)
{
String loca = variable.substring(0, variable.indexOf("."));
DBObject obj = supportingData.get(loca);
if (obj != null)
varValue = "" + obj.get( variable.substring(variable.indexOf(".") + 1) );
}
else if (other != null)
varValue = "" + other.get( variable );
if (varValue == null || "null".equalsIgnoreCase(varValue))
varValue = "";
String newValue = text.substring(0, start);
newValue += varValue;
newValue += text.substring(end + 1, text.length());
text = newValue;
}
return (text == null ? "" : text);
}
public static void processAppleFeedbackService()
{
try {
List inactiveDevices = Push.feedback("pushtest-push.p12", "Jedman123!", false);
logger.info(inactiveDevices.size() + " devices inactive - flushing...");
for (Device dev : inactiveDevices)
{
List notifList = NotificationInst.getNotification("ios-push", dev.getDeviceId());
for (NotificationInst notif : notifList)
notif.disable("Feedback Service Disabled");
}
} catch (KeystoreException e) {
/* A critical problem occurred while trying to use your keystore */
logger.error("Error while communicating with iOS Push [CERT]", e);
} catch (CommunicationException e) {
/* A critical communication error occurred while trying to contact Apple servers */
logger.warn("Error while communicating with iOS Push [COMMS]", e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy