package org.mobicents.slee.services.sip.registrar;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sip.InvalidArgumentException;
import javax.sip.RequestEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.header.CSeqHeader;
import javax.sip.header.CallIdHeader;
import javax.sip.header.ContactHeader;
import javax.sip.header.DateHeader;
import javax.sip.header.ExpiresHeader;
import javax.sip.header.HeaderAddress;
import javax.sip.header.HeaderFactory;
import javax.sip.header.ToHeader;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;
import javax.slee.ActivityContextInterface;
import javax.slee.ActivityEndEvent;
import javax.slee.ChildRelation;
import javax.slee.CreateException;
import javax.slee.RolledBackContext;
import javax.slee.SLEEException;
import javax.slee.Sbb;
import javax.slee.SbbContext;
import javax.slee.TransactionRequiredLocalException;
import javax.slee.serviceactivity.ServiceActivity;
import javax.slee.serviceactivity.ServiceActivityFactory;
import net.java.slee.resource.sip.SipActivityContextInterfaceFactory;
import net.java.slee.resource.sip.SleeSipProvider;
import org.apache.log4j.Logger;
import org.mobicents.slee.services.sip.location.LocationSbbLocalObject;
import org.mobicents.slee.services.sip.location.LocationServiceException;
import org.mobicents.slee.services.sip.location.RegistrationBinding;
import org.mobicents.slee.services.sip.registrar.mbean.RegistrarConfigurator;
/**
*
* this is the abstract class that will be deployed
*
* Sbb abstract class provided by the Sbb developer
*/
public abstract class RegistrarSbb implements Sbb {
private static Logger logger = Logger.getLogger(RegistrarSbb.class);
/**
* MBean Configurator
*/
private static final RegistrarConfigurator config = new RegistrarConfigurator();
private SleeSipProvider provider=null;
private AddressFactory addressFactory;
private HeaderFactory headerFactory;
private MessageFactory messageFactory;
private SipActivityContextInterfaceFactory acif;
private SbbContext sbbContext;
private Context sbbEnv;
public void setSbbContext(SbbContext context) {
this.sbbContext = context;
try {
sbbEnv = (Context) new InitialContext().lookup("java:comp/env");
provider = (SleeSipProvider) sbbEnv.lookup("slee/resources/jainsip/1.2/provider");
messageFactory = provider.getMessageFactory();
headerFactory = provider.getHeaderFactory();
addressFactory = provider.getAddressFactory();
acif = (SipActivityContextInterfaceFactory) sbbEnv.lookup("slee/resources/jainsip/1.2/acifactory");
} catch (NamingException ne) {
logger.error("Could not set SBB context: ", ne);
}
}
// **** SETUP CONFIGURATION MBEAN
public void onServiceStarted(
javax.slee.serviceactivity.ServiceStartedEvent serviceEvent,
ActivityContextInterface aci) {
try {
// check if it's my service that is starting
ServiceActivity sa = ((ServiceActivityFactory) sbbEnv
.lookup("slee/serviceactivity/factory")).getActivity();
if (sa.equals(aci.getActivity())) {
config.startService();
logger.info("Registrar Configuration MBean started");
getLocationSbb().init();
}
else {
aci.detach(this.sbbContext.getSbbLocalObject());
}
} catch (Exception e) {
logger.error(e);
}
}
public void onActivityEndEvent(ActivityEndEvent event, ActivityContextInterface aci) {
try {
// check if it's my service that is starting
ServiceActivity sa = ((ServiceActivityFactory) sbbEnv
.lookup("slee/serviceactivity/factory")).getActivity();
if (sa.equals(aci.getActivity())) {
config.stopService();
getLocationSbb().shutdown();
}
} catch (Exception e) {
logger.error(e);
}
}
// **** REGISTER REQUEST PROCESSING
public void onRegisterEvent(RequestEvent event, ActivityContextInterface ac) {
if (logger.isDebugEnabled()) {
logger.debug("onRegisterEvent:\n request="+event.getRequest());
}
// detach from this server transaction activity, we don't want to handle activity end event
ac.detach(this.sbbContext.getSbbLocalObject());
try {
// see if child sbb local object is already in CMP
LocationSbbLocalObject locationService = getLocationSbb();
// get configuration from MBean
final long maxExpires=config.getSipRegistrationMaxExpires();
final long minExpires=config.getSipRegistrationMinExpires();
// Process require header
// Authenticate
// Authorize
// OK we're authorized now ;-)
// extract address-of-record
String sipAddressOfRecord = getCanonicalAddress((HeaderAddress) event.getRequest()
.getHeader(ToHeader.NAME));
if (logger.isDebugEnabled()) {
logger.debug("onRegisterEvent: address-of-record from request= " + sipAddressOfRecord);
}
// map will be empty if user not in LS...
// Note we don't care if the user has a valid account in the LS, we
// just add them anyway.
String sipAddress = getCanonicalAddress((HeaderAddress) event.getRequest()
.getHeader(ToHeader.NAME));
Map<String, RegistrationBinding> bindings = locationService
.getBindings(sipAddress);
// Do we have any contact header(s)?
if (event.getRequest().getHeader(ContactHeader.NAME) == null) {
// Just send OK with current bindings - this request was a
// query.
logger.info("query for bindings: sipAddress="+sipAddress);
sendRegistrationOKResponse(event.getServerTransaction(), event.getRequest(), bindings);
return;
}
// Check contact, callid, cseq
ArrayList newContacts = getContactHeaderList(event.getRequest()
.getHeaders(ContactHeader.NAME));
final String callId = ((CallIdHeader) event.getRequest()
.getHeader(CallIdHeader.NAME)).getCallId();
final long seq = ((CSeqHeader) event.getRequest()
.getHeader(CSeqHeader.NAME)).getSeqNumber();
ExpiresHeader expiresHeader = event.getRequest().getExpires();
if (hasWildCard(newContacts)) { // This is a "Contact: *" "remove
// all bindings" request
if ((expiresHeader == null)
|| (expiresHeader.getExpires() != 0)
|| (newContacts.size() > 1)) {
// malformed request in RFC3261 ch10.3 step 6
sendErrorResponse(Response.BAD_REQUEST,event.getServerTransaction(),event.getRequest());
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Removing bindings");
}
// Go through list of current bindings
// if callid doesn't match - remove binding
// if callid matches and seq greater, remove binding.
Iterator<RegistrationBinding> it = bindings.values().iterator();
try {
while (it.hasNext()) {
RegistrationBinding binding = (RegistrationBinding) it
.next();
if (callId.equals(binding.getCallId())) {
if (seq > binding.getCSeq()) {
it.remove();
locationService.removeBinding(sipAddressOfRecord,
binding.getContactAddress());
} else {
sendErrorResponse(Response.BAD_REQUEST,event.getServerTransaction(),event.getRequest());
return;
}
} else {
it.remove();
locationService.removeBinding(sipAddressOfRecord, binding
.getContactAddress());
}
}
} catch (LocationServiceException lse) {
logger.error(lse);
sendErrorResponse(Response.SERVER_INTERNAL_ERROR,event.getServerTransaction(),event.getRequest());
return;
}
sendRegistrationOKResponse(event.getServerTransaction(), event.getRequest(), bindings);
}
else {
// Update bindings
if (logger.isDebugEnabled()) {
logger.debug("Updating bindings");
}
ListIterator li = newContacts.listIterator();
while (li.hasNext()) {
ContactHeader contact = (ContactHeader) li.next();
// get expires value, either in header or default
// do min-expires etc
long requestedExpires = 0;
if (contact.getExpires() >= 0) {
requestedExpires = contact.getExpires();
} else if ((expiresHeader != null)
&& (expiresHeader.getExpires() >= 0)) {
requestedExpires = expiresHeader.getExpires();
} else {
requestedExpires = 3600; // default
}
// If expires too large, reset to our local max
if (requestedExpires > maxExpires) {
requestedExpires = maxExpires;
} else if ((requestedExpires > 0)
&& (requestedExpires < minExpires)) {
// requested expiry too short, send response with
// min-expires
//
sendIntervalTooBriefResponse(event.getServerTransaction(), event.getRequest(), minExpires);
return;
}
// Get the q-value (preference) for this binding - default
// to 0.0 (min)
float q = 0;
if (contact.getQValue() != -1)
q = contact.getQValue();
if ((q > 1) || (q < 0)) {
sendErrorResponse(Response.BAD_REQUEST,event.getServerTransaction(),event.getRequest());
return;
}
// Find existing binding
String contactAddress = contact.getAddress().getURI().toString();
RegistrationBinding binding = (RegistrationBinding) bindings
.get(contactAddress);
if (binding != null) { // Update this binding
if (callId.equals(binding.getCallId())) {
if (seq <= binding.getCSeq()) {
sendErrorResponse(Response.BAD_REQUEST,event.getServerTransaction(),event.getRequest());
return;
}
}
if (requestedExpires == 0) {
if (logger.isDebugEnabled()) {
logger.debug("Removing binding: "
+ sipAddressOfRecord + " -> "
+ contactAddress);
}
bindings.remove(contactAddress);
locationService.removeBinding(sipAddressOfRecord,
binding.getContactAddress());
} else {
if (logger.isDebugEnabled()) {
logger.debug("Updating binding: "
+ sipAddressOfRecord + " -> "
+ contactAddress);
logger.debug("contact: " + contact.toString());
}
// Lets push it into location service, this will
// update version of binding
binding.setCallId(callId);
binding.setExpires(requestedExpires);
binding.setRegistrationDate(System.currentTimeMillis());
binding.setCSeq(seq);
binding.setQValue(q);
locationService.updateBinding(binding);
}
} else {
// Create new binding
if (requestedExpires != 0) {
if (logger.isDebugEnabled()) {
logger.debug("Adding new binding: "
+ sipAddressOfRecord + " -> "
+ contactAddress);
logger.debug(contact.toString());
}
// removed comment parameter to registration binding
// - Address and Contact headers don't have comments
// in 1.1
RegistrationBinding registrationBinding = locationService
.addBinding(sipAddress,
contactAddress, "",
requestedExpires, System.currentTimeMillis(), q, callId,
seq);
bindings.put(registrationBinding.getContactAddress(), registrationBinding);
}
}
}
// Update bindings, return 200 if successful, 500 on error
sendRegistrationOKResponse(event.getServerTransaction(), event.getRequest(), bindings);
}
} catch (Exception e) {
// Send error response so client can deal with it
logger.warn("Exception during request processing", e);
try {
sendErrorResponse(Response.SERVER_INTERNAL_ERROR,event.getServerTransaction(),event.getRequest());
} catch (Exception ex) {
logger.error(e);
}
}
}
// ------------------- helper methods
private String getCanonicalAddress(HeaderAddress header) {
String addr = header.getAddress().getURI().toString();
int index = addr.indexOf(':');
index = addr.indexOf(':', index + 1);
if (index != -1) {
// Get rid of the port
addr = addr.substring(0, index);
}
return addr;
}
private List<ContactHeader> getContactHeaders(
Collection<RegistrationBinding> bindings) {
if (bindings == null)
return null;
ArrayList<ContactHeader> contactHeaders = new ArrayList<ContactHeader>();
Iterator<RegistrationBinding> it = bindings.iterator();
while (it.hasNext()) {
RegistrationBinding binding = it.next();
try {
Address contactAddress = addressFactory.createAddress(binding.getContactAddress());
javax.sip.header.ContactHeader contactHeader = headerFactory.createContactHeader(contactAddress);
// String comment = getComment();
contactHeader.setExpires((int)binding.getExpiresDelta());
contactHeader.setQValue(binding.getQValue());
contactHeaders.add(contactHeader);
} catch (Exception e) {
logger.warn("Failed to create contact headers",e);
}
}
return contactHeaders;
}
private ArrayList getContactHeaderList(ListIterator it) {
ArrayList l = new ArrayList();
while (it.hasNext()) {
l.add(it.next());
}
return l;
}
private void sendRegistrationOKResponse(
ServerTransaction serverTransaction, Request request,
Map<String, RegistrationBinding> bindings) throws ParseException,
SipException, InvalidArgumentException {
List contactHeaders = getContactHeaders(bindings.values());
Response res = (this.messageFactory
.createResponse(Response.OK, request));
if ((contactHeaders != null) && (!contactHeaders.isEmpty())) {
if (logger.isDebugEnabled()) {
logger.debug("Adding " + contactHeaders.size() + " headers");
}
for (int i = 0; i < contactHeaders.size(); i++) {
ContactHeader hdr = (ContactHeader) contactHeaders.get(i);
res.addHeader(hdr);
}
}
DateHeader dateHeader = this.headerFactory
.createDateHeader(new GregorianCalendar());
res.setHeader(dateHeader);
if (logger.isInfoEnabled()) {
logger.info("sending response:\n" + res);
}
serverTransaction.sendResponse(res);
}
private void sendErrorResponse(int responseCode, ServerTransaction serverTransaction, Request request) throws ParseException, SipException, InvalidArgumentException {
Response response = this.messageFactory.createResponse(responseCode, request);
serverTransaction.sendResponse(response);
if (logger.isInfoEnabled()) {
logger.info("sending response:\n" + response);
}
}
private void sendIntervalTooBriefResponse(
ServerTransaction serverTransaction, Request request,
long minExpires) throws ParseException, SipException,
InvalidArgumentException {
Response res = this.messageFactory.createResponse(
Response.INTERVAL_TOO_BRIEF, request);
// set date header
res.setHeader(this.headerFactory
.createDateHeader(new GregorianCalendar()));
// set min expires header
res.addHeader(this.headerFactory.createHeader("Min-Expires", Long
.toString(minExpires)));
if (logger.isInfoEnabled()) {
logger.info("sending response:\n" + res.toString());
}
serverTransaction.sendResponse(res);
}
private boolean hasWildCard(ArrayList contactHeaders) {
Iterator it = contactHeaders.iterator();
while (it.hasNext()) {
ContactHeader header = (ContactHeader) it.next();
if (header.toString().indexOf('*') > 0)
return true;
}
return false;
}
// location service child relation
public abstract ChildRelation getLocationSbbChildRelation();
public LocationSbbLocalObject getLocationSbb() throws TransactionRequiredLocalException, SLEEException, CreateException {
return (LocationSbbLocalObject) getLocationSbbChildRelation().create();
}
// usuall stuff
public void unsetSbbContext() { sbbContext = null; }
public void sbbCreate() throws CreateException {}
public void sbbPostCreate() throws CreateException {}
public void sbbRemove() {}
public void sbbPassivate() {}
public void sbbActivate() {}
public void sbbLoad() {}
public void sbbStore() {}
public void sbbExceptionThrown(Exception exception, Object event,
ActivityContextInterface aci) {}
public void sbbRolledBack(RolledBackContext context) {}
}