package org.mobicents.slee.sipevent.server.publication; import gov.nist.javax.sip.header.ims.PChargingFunctionAddressesHeader; import gov.nist.javax.sip.header.ims.PChargingVectorHeader; import javax.naming.Context; import javax.naming.InitialContext; import javax.sip.ListeningPoint; import javax.sip.RequestEvent; import javax.sip.ServerTransaction; import javax.sip.address.Address; import javax.sip.address.AddressFactory; import javax.sip.address.SipURI; import javax.sip.address.URI; import javax.sip.header.ContactHeader; import javax.sip.header.ContentTypeHeader; import javax.sip.header.EventHeader; import javax.sip.header.ExpiresHeader; import javax.sip.header.HeaderFactory; import javax.sip.header.SIPIfMatchHeader; 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.Sbb; import javax.slee.SbbContext; import javax.slee.SbbLocalObject; import javax.slee.serviceactivity.ServiceActivity; import javax.slee.serviceactivity.ServiceActivityFactory; import javax.slee.serviceactivity.ServiceStartedEvent; import net.java.slee.resource.sip.SleeSipProvider; import org.apache.log4j.Logger; import org.mobicents.slee.sipevent.server.publication.jmx.PublicationControlManagement; import org.mobicents.slee.sipevent.server.publication.jmx.PublicationControlManagementMBean; /** * Sbb to control publication of sip events. * * @author Eduardo Martins * */ public abstract class SipPublicationControlSbb implements Sbb, PublicationClientControlParentSbbLocalObject { private static Logger logger = Logger.getLogger(SipPublicationControlSbb.class); /** * JAIN-SIP provider & factories * * @return */ private SleeSipProvider sipProvider; private AddressFactory addressFactory; private MessageFactory messageFactory; private HeaderFactory headerFactory; /** * SbbObject's sbb context */ protected SbbContext sbbContext; private Context jndiContext; /** * SbbObject's context setting */ public void setSbbContext(SbbContext sbbContext) { this.sbbContext=sbbContext; // retrieve factories, facilities & providers try { jndiContext = (Context) new InitialContext().lookup("java:comp/env"); sipProvider = (SleeSipProvider)jndiContext.lookup("slee/resources/jainsip/1.2/provider"); addressFactory = sipProvider.getAddressFactory(); headerFactory = sipProvider.getHeaderFactory(); messageFactory = sipProvider.getMessageFactory(); } catch (Exception e) { logger.error("Unable to retrieve factories, facilities & providers",e); } } // --- INTERNAL CHILD SBB public abstract ChildRelation getPublicationControlChildRelation(); public abstract PublicationControlSbbLocalObject getPublicationControlChildSbbCMP(); public abstract void setPublicationControlChildSbbCMP(PublicationControlSbbLocalObject value); private PublicationControlSbbLocalObject getPublicationControlChildSbb() { PublicationControlSbbLocalObject childSbb = getPublicationControlChildSbbCMP(); if (childSbb == null) { try { childSbb = (PublicationControlSbbLocalObject) getPublicationControlChildRelation().create(); } catch (Exception e) { logger.error("Failed to create child sbb",e); return null; } setPublicationControlChildSbbCMP(childSbb); childSbb.setParentSbb((PublicationClientControlParentSbbLocalObject)this.sbbContext.getSbbLocalObject()); } return childSbb; } // -- CONFIGURATION /** * the Management MBean */ private static final PublicationControlManagement configuration = new PublicationControlManagement(); /** * Retrieves the current configuration for this component from an MBean * @return */ public static PublicationControlManagementMBean getConfiguration() { return configuration; } // ----------- EVENT HANDLERS /** * PUBLISH event processing * * @param event * @param aci */ public void onPublish(RequestEvent event, ActivityContextInterface aci) { // detach from aci, we don't want ot handle the activity end event SbbLocalObject sbbLocalObject = this.sbbContext.getSbbLocalObject(); aci.detach(sbbLocalObject); if (logger.isDebugEnabled()) { logger.debug("Processing PUBLISH request..."); } // get child sbb that handles all the publication logic PublicationControlSbbLocalObject childSbb = getPublicationControlChildSbb(); if (childSbb == null) { try { // create response Response response = messageFactory.createResponse(Response.SERVER_INTERNAL_ERROR,event.getRequest()); event.getServerTransaction().sendResponse(response); if (logger.isDebugEnabled()) { logger.debug("Response sent:\n"+response.toString()); } } catch (Exception f) { logger.error("Can't send error response!",f); } return; } /* * The presence of a body and the SIP-If-Match header field * determine the specific operation that the request is performing, * as described in Table 1. * +-----------+-------+---------------+---------------+ * | Operation | Body? | SIP-If-Match? | Expires Value | * +-----------+-------+---------------+---------------+ * | Initial | yes | no | > 0 | * | Refresh | no | yes | > 0 | * | Modify | yes | yes | > 0 | * | Remove | no | yes | 0 | * +-----------+-------+---------------+---------------+ * Table 1: Publication Operations * * If expires does not exist then the service must choose it's value */ // get event header EventHeader eventHeader = (EventHeader) event.getRequest().getHeader( EventHeader.NAME); if (eventHeader != null) { // check event package String eventPackage = eventHeader.getEventType(); if (acceptsEventPackage(eventPackage,childSbb)) { URI entityURI = event.getRequest().getRequestURI(); // The ESC inspects the Request-URI to determine whether this request // is targeted to a resource for which the ESC is responsible for // maintaining event state. If not, the ESC MUST return a 404 (Not // Found) response and skip the remaining steps. if (childSbb.isResponsibleForResource(entityURI)) { // process expires header ExpiresHeader expiresHeader = event.getRequest().getExpires(); int expires; // if expires does not exist then set it's value to default // value if (expiresHeader == null) { expires = getConfiguration().getDefaultExpires(); } else { expires = expiresHeader.getExpires(); } // check expires value if (expires > 0) { // check if expires is not less than the allowed min expires if (expires >= getConfiguration().getMinExpires()) { // ensure expires is not bigger than max expires if (expires > getConfiguration().getMaxExpires()) { expires = getConfiguration().getMaxExpires(); } String entity = entityURI.toString(); // new publication or publication refresh ? SIPIfMatchHeader sipIfMatchHeader = (SIPIfMatchHeader)event.getRequest().getHeader(SIPIfMatchHeader.NAME); if (sipIfMatchHeader != null) { // refresh or modification of publication if (event.getRequest().getContentLength().getContentLength() == 0) { // refreshing a publication childSbb.refreshPublication(event, entity, eventPackage, sipIfMatchHeader.getETag(), expires); } else { ContentTypeHeader contentTypeHeader = (ContentTypeHeader) event.getRequest().getHeader(ContentTypeHeader.NAME); if (childSbb.acceptsContentType(eventPackage,contentTypeHeader)) { // modification childSbb.modifyPublication(event, entity, eventPackage, sipIfMatchHeader.getETag(), new String(event.getRequest().getRawContent()), contentTypeHeader.getContentType(), contentTypeHeader.getContentSubType(), expires); } else { // unsupported media type, send the ones supported sendErrorResponse(Response.UNSUPPORTED_MEDIA_TYPE,event.getRequest(),event.getServerTransaction(),eventPackage,childSbb); } } } else { // new publication if (event.getRequest().getContentLength().getContentLength() != 0) { ContentTypeHeader contentTypeHeader = (ContentTypeHeader) event.getRequest().getHeader(ContentTypeHeader.NAME); if (childSbb.acceptsContentType(eventPackage,contentTypeHeader)) { childSbb.newPublication(event, entityURI.toString(), eventPackage, new String(event.getRequest().getRawContent()), contentTypeHeader.getContentType(), contentTypeHeader.getContentSubType(), expires); } else { // unsupported media type, send the one supported sendErrorResponse(Response.UNSUPPORTED_MEDIA_TYPE,event.getRequest(),event.getServerTransaction(),eventPackage,childSbb); } } else { // send Bad Request since there is no content sendErrorResponse(Response.BAD_REQUEST,event.getRequest(),event.getServerTransaction(),eventPackage,childSbb); } } } else { // expires is > 0 but < min expires, respond (Interval // Too Brief) with Min-Expires = MINEXPIRES sendErrorResponse(Response.INTERVAL_TOO_BRIEF, event .getRequest(), event.getServerTransaction(),eventPackage,childSbb); } } else if (expires == 0) { String entity = event.getRequest().getRequestURI().toString(); SIPIfMatchHeader sipIfMatchHeader = (SIPIfMatchHeader)event.getRequest().getHeader(SIPIfMatchHeader.NAME); if (sipIfMatchHeader != null) { // remove publication childSbb.removePublication(event, entity, eventPackage, sipIfMatchHeader.getETag()); } else { // send Bad Request since removal requires etag sendErrorResponse(Response.BAD_REQUEST,event.getRequest(),event.getServerTransaction(),eventPackage,childSbb); } } else { // expires can't be negative sendErrorResponse(Response.BAD_REQUEST, event.getRequest(), event.getServerTransaction(),eventPackage,childSbb); } } else { // not responsible for this resource sendErrorResponse(Response.NOT_FOUND, event.getRequest(), event.getServerTransaction(), eventPackage,childSbb); } } else { // wrong event package, send bad event type error sendErrorResponse(Response.BAD_EVENT, event.getRequest(), event .getServerTransaction(),eventPackage,childSbb); } } else { // subscribe does not have a event header sendErrorResponse(Response.BAD_REQUEST, event.getRequest(), event .getServerTransaction(),null,childSbb); } } public void onOptions(RequestEvent requestEvent, ActivityContextInterface aci) { if (logger.isInfoEnabled()) { logger.info("options event received but server does not supports it"); } aci.detach(this.sbbContext.getSbbLocalObject()); /* * A client may probe the ESC for the support of PUBLISH using the * OPTIONS request defined in SIP [4]. The ESC processes OPTIONS * requests as defined in Section 11.2 of RFC 3261 [4]. In the response * to an OPTIONS request, the ESC SHOULD include "PUBLISH" to the list * of allowed methods in the Allow header field. Also, it SHOULD list * the supported event packages in an Allow-Events header field. * * The Allow header field may also be used to specifically announce * support for PUBLISH messages when registering. (See SIP Capabilities * [12] for details). */ // TODO } public void onServiceStartedEvent(ServiceStartedEvent event, ActivityContextInterface aci) { // we want to stay attached to this service activity, to receive the activity end event on service deactivation try { //get this service activity ServiceActivity sa = ((ServiceActivityFactory) jndiContext.lookup("slee/serviceactivity/factory")).getActivity(); if (!sa.equals(aci.getActivity())) { aci.detach(this.sbbContext.getSbbLocalObject()); } else { // starts the mbean configuration.startService(); } } catch (Exception e) { logger.error("failed to process service started event",e); } } public void onActivityEndEvent(ActivityEndEvent event, ActivityContextInterface aci) { // shutdown internal interface try { PublicationControlSbbLocalObject childSbb = getPublicationControlChildSbb(); if (childSbb != null) { childSbb.shutdown(); } // stop mbean configuration.stopService(); } catch (Exception e) { logger.error("Faield to shutdown publication control",e); } } // ----------- CALL BACK METHODS FOR CHILD SBB public void modifyPublicationError(Object requestId, int error) { sendErrorResponse(requestId,error); } public void modifyPublicationOk(Object requestId, String tag, int expires) throws Exception { sendOkResponse((RequestEvent)requestId, tag, expires); } public void newPublicationError(Object requestId, int error) { sendErrorResponse(requestId,error); } public void newPublicationOk(Object requestId, String tag,int expires) throws Exception { sendOkResponse((RequestEvent)requestId, tag, expires); } public void refreshPublicationError(Object requestId, int error) { sendErrorResponse(requestId,error); } public void refreshPublicationOk(Object requestId, String tag, int expires) throws Exception { sendOkResponse((RequestEvent)requestId, tag, expires); } public void removePublicationError(Object requestId, int error) { sendErrorResponse(requestId,error); } public void removePublicationOk(Object requestId) throws Exception { sendOkResponse((RequestEvent)requestId, null, -1); } // ----------- AUX METHODS private void sendOkResponse(RequestEvent event,String eTag,int expires) throws Exception { // send 200 ok response with expires and sipEtag Response response = messageFactory.createResponse(Response.OK, event.getRequest()); if (eTag != null) response.addHeader(headerFactory.createSIPETagHeader(eTag)); if (expires != -1) response.addHeader(headerFactory.createExpiresHeader(expires)); // Both 2xx response to SUBSCRIBE and NOTIFY need a Contact if (response.getHeader(ContactHeader.NAME) != null) { response.removeHeader(ContactHeader.NAME); } /* aayush..started adding here: (Ref issue #567) * The PUBLISH request had a P-charging-vector header * that consisted of an ICID and an orig-ioi parameter.The PS * needs to preserve that header and needs to add a term-ioi * parameter pointing to its own domain name:*/ // 1. Get the header as received from the request: PChargingVectorHeader pcv = (PChargingVectorHeader) event.getRequest().getHeader(PChargingVectorHeader.NAME); // 2. In case the request is received from a non-IMS VoIP network,P-charging-Vector wont be there,so check for pcv!=null if(pcv!=null) { pcv.setTerminatingIOI(configuration.getPChargingVectorHeaderTerminatingIOI()); response.addHeader(pcv); } // Also need to add the P-Charging-Function-Addresses header // as received from the PUBLISH request in the 200 OK being sent: // 1. Get the header from the request: PChargingFunctionAddressesHeader pcfa = (PChargingFunctionAddressesHeader) event.getRequest().getHeader(PChargingFunctionAddressesHeader.NAME); // 2. Add the header without any changes: if(pcfa!=null) response.addHeader(pcfa); // end of additions...aayush. try { ListeningPoint listeningPoint = sipProvider.getListeningPoint("udp"); Address address = addressFactory.createAddress( getConfiguration().getContactAddressDisplayName()+ " <sip:" + listeningPoint.getIPAddress() + ">"); ((SipURI) address.getURI()).setPort(listeningPoint.getPort()); response.addHeader(headerFactory.createContactHeader(address)); } catch (Exception e) { logger.error("Can't add contact header", e); } event.getServerTransaction().sendResponse(response); if (logger.isDebugEnabled()) { logger.debug("Response sent:\n"+response.toString()); } } private void sendErrorResponse(Object requestId, int error) { RequestEvent event = (RequestEvent) requestId; sendErrorResponse(error, event.getRequest(), event .getServerTransaction(), ((EventHeader) event.getRequest() .getHeader(EventHeader.NAME)).getEventType(), getPublicationControlChildSbb()); } /* * Sends an error response with the specified status code, adding additional * headers if needed */ private void sendErrorResponse(int responseCode, Request request, ServerTransaction serverTransaction, String eventPackage, PublicationControlSbbLocalObject childSbb) { try { // create response Response response = messageFactory.createResponse(responseCode,request); // add headers if needed if (responseCode == Response.BAD_EVENT && childSbb != null) { String allowEventsHeader = ""; boolean first = true; for (String acceptedEventPackage : childSbb.getEventPackages()) { if (first) { allowEventsHeader += acceptedEventPackage; first = false; } else { allowEventsHeader += ","+acceptedEventPackage; } } response .addHeader(headerFactory.createAllowEventsHeader(allowEventsHeader)); } else if (responseCode == Response.INTERVAL_TOO_BRIEF) response.addHeader(headerFactory.createMinExpiresHeader(getConfiguration().getMinExpires())); else if (responseCode == Response.UNSUPPORTED_MEDIA_TYPE && childSbb != null) { response.addHeader(childSbb.getAcceptsHeader(eventPackage)); } serverTransaction.sendResponse(response); if (logger.isDebugEnabled()) { logger.debug("Response sent:\n"+response.toString()); } } catch (Exception e) { logger.error("Can't send response!",e); } } /** * verifies if the specified event packaged is accepted */ private boolean acceptsEventPackage(String eventPackage,PublicationControlSbbLocalObject childSbb) { if (eventPackage != null) { for(String acceptedEventPackage : childSbb.getEventPackages()) { if (eventPackage.equals(acceptedEventPackage)) { return true; } } } return false; } // ----------- SBB OBJECT's LIFE CYCLE public void sbbActivate() {} public void sbbCreate() throws CreateException {} public void sbbExceptionThrown(Exception arg0, Object arg1, ActivityContextInterface arg2) {} public void sbbLoad() {} public void sbbPassivate() {} public void sbbPostCreate() throws CreateException {} public void sbbRemove() {} public void sbbRolledBack(RolledBackContext arg0) {} public void sbbStore() {} public void unsetSbbContext() { this.sbbContext = null; } }