package com.rayo.provisioning;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import com.google.gson.GsonBuilder;
import com.rayo.provisioning.model.AddressNotification;
import com.rayo.provisioning.model.UpdateNotification;
import com.rayo.provisioning.storage.StorageServiceClient;
import com.rayo.server.storage.model.Application;
import com.voxeo.logging.Loggerf;
/**
* <p>A {@link MessageProcessor} is in charge of processing the messages coming from the
* provisioning API. It will pick messages from JMS, transform them to JSON and parse
* the JSON content invoking appropriate methods in the storage service.</p>
*
* @author martin
*
*/
public class MessageProcessor implements MessageListener {
private static Loggerf logger = Loggerf.getLogger(MessageProcessor.class);
private AtomicLong messagesProcessed = new AtomicLong();
private AtomicLong messagesFailed = new AtomicLong();
private StorageServiceClient storageServiceClient;
private ProvisioningClient provisioningServiceClient;
private String domainName = "localhost";
@Override
public void onMessage(Message message) {
logger.debug("Received message %s", message);
if (message instanceof TextMessage) {
try {
String body = ((TextMessage)message).getText();
messagesProcessed.incrementAndGet();
GsonBuilder builder = new GsonBuilder();
UpdateNotification notification = builder.create().fromJson(body, UpdateNotification.class);
logger.debug("Found notification %s", notification);
if (notification.getAppId() != null) {
processApplication(notification);
} else {
processAccount(notification);
}
} catch (Exception e) {
logger.error("Could not handle message: " + e.getMessage(), e);
messagesFailed.incrementAndGet();
}
} else {
logger.error("Unrecognized type of message " + message.getClass());
messagesFailed.incrementAndGet();
}
}
private void processApplication(UpdateNotification notification) throws Exception {
if (isValidApplicationJid(notification.getVoiceUrl())) {
// Fetch the application from the provisioning service
Application application = provisioningServiceClient.getApplication(notification.getAccountId(), notification.getAppId());
if (application == null) {
logger.debug("Application [%s] not found.", notification.getAppId());
storageServiceClient.removeApplication(notification.getVoiceUrl());
} else {
if (application.getPlatform().equalsIgnoreCase("rayo")) {
// Find in Rayo
Application rayoApplication = storageServiceClient.findApplication(application.getBareJid());
if (rayoApplication == null) {
logger.debug("Application [%s] not found on Rayo. Creating it", application.getBareJid());
application.setAccountId(notification.getAccountId());
application.setPermissions(provisioningServiceClient.getFeatures(notification.getAccountId()));
storageServiceClient.createApplication(application);
} else {
storageServiceClient.updateApplication(application);
}
if (!notification.getAddresses().isEmpty()) {
processAddresses(notification);
}
} else {
logger.debug("Received notification for a non Rayo application. Dropping it off.");
}
}
}
}
private boolean isValidApplicationJid(String voiceUrl) {
// Naive, but should be enought for rayo apps
if (!voiceUrl.contains("@")) return false;
if (voiceUrl.contains("/")) return false;
String[] parts = voiceUrl.split("@");
if (parts.length != 2) return false;
if (parts[0].trim().equals("") || parts[1].trim().equals("")) return false;
return true;
}
private void processAccount(UpdateNotification notification) throws Exception {
// Check if features were updated
String newFeatures = provisioningServiceClient.getFeatures(notification.getAccountId());
List<Application> applications = provisioningServiceClient.getApplications(notification.getAccountId());
for(Application application: applications) {
Application rayoApplication = storageServiceClient.findApplication(application.getBareJid());
if (!rayoApplication.getPermissions().equals(newFeatures)) {
rayoApplication.setPermissions(newFeatures);
storageServiceClient.updateApplication(rayoApplication);
}
}
processAddresses(notification);
}
private void processAddresses(UpdateNotification notification) throws Exception {
// Get list of current addresses from the provisioning api
List<String> currentAddresses = null;
if (notification.getAppId() != null) {
currentAddresses = provisioningServiceClient.getAddresses(notification.getAppId());
} else {
currentAddresses = provisioningServiceClient.getAddressesForAccount(notification.getAccountId());
}
// Load all applications
Map<String, Application> applicationsMap = loadApplications(notification);
// Get the current list of addresses that are stored in the Rayo routing database
List<String> rayoAddresses = loadRayoAddresses(notification, applicationsMap);
for (AddressNotification address: notification.getAddresses()) {
String jid = notification.getVoiceUrl();
if (jid == null) {
Application app = applicationsMap.get(address.getAppId());
if (app != null) {
jid = app.getBareJid();
} else {
logger.error("Received a notification but could not find application with id [%s]. Skipping.", address.getAppId());
continue;
}
}
if (!isValidApplicationJid(jid)) {
logger.debug("Received notificaiton from app [%s] but its voice url is not a valid jid [%s]. Skipping.",
address.getAppId(), jid);
continue;
}
String addr = createAddress(address);
if (!rayoAddresses.contains(addr)) {
// new address
storageServiceClient.storeAddress(jid, addr);
} else {
if (!currentAddresses.contains(addr)) {
// delete
storageServiceClient.removeAddressFromApplication(addr);
} else {
// update?
}
}
}
}
private String createAddress(AddressNotification address) {
if (address.getType().equalsIgnoreCase("sip")) {
StringBuilder buffer = new StringBuilder();
if (!address.getAddress().startsWith("sip:")) {
buffer.append("sip:");
}
buffer.append(address.getAddress());
if (buffer.indexOf("@") == -1) {
// set default domain
buffer.append("@" + domainName);
}
return buffer.toString();
} else {
return address.getAddress();
}
}
private Map<String, Application> loadApplications(UpdateNotification notification) throws Exception {
Map<String, Application> apps = new HashMap<String, Application>();
List<Application> applications = provisioningServiceClient.getApplications(notification.getAccountId());
for(Application application: applications) {
apps.put(application.getAppId(), application);
}
return apps;
}
private List<String> loadRayoAddresses(UpdateNotification notification,
Map<String, Application> applicationsMap) throws Exception {
List<String> addresses = new ArrayList<String>();
List<String> processedApps = new ArrayList<String>();
for (AddressNotification address: notification.getAddresses()) {
String appId = notification.getAppId();
if (address.getAppId() != null) {
appId = address.getAppId();
}
if (appId != null) {
if (!processedApps.contains(appId)) {
Application application = applicationsMap.get(appId);
if (application != null) {
addresses.addAll(storageServiceClient.findAddressesForApplication(application.getBareJid()));
}
processedApps.add(appId);
}
}
}
return addresses;
}
protected long getMessagesProcessed() {
return messagesProcessed.get();
}
protected long getMessagesFailed() {
return messagesFailed.get();
}
protected void setStorageServiceClient(StorageServiceClient storageServiceClient) {
this.storageServiceClient = storageServiceClient;
}
public void setProvisioningServiceClient(
ProvisioningClient provisioningServiceClient) {
this.provisioningServiceClient = provisioningServiceClient;
}
public void setDomainName(String domainName) {
this.domainName = domainName;
}
}