package org.mobicents.slee.examples.diameter.openims; import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Properties; import javax.naming.Context; import javax.naming.InitialContext; import javax.sip.ClientTransaction; import javax.sip.ResponseEvent; import javax.sip.address.Address; import javax.sip.address.AddressFactory; import javax.sip.address.SipURI; import javax.sip.address.URI; import javax.sip.header.ContentTypeHeader; import javax.sip.header.FromHeader; import javax.sip.header.HeaderFactory; import javax.sip.header.MaxForwardsHeader; import javax.sip.header.ToHeader; import javax.sip.header.ViaHeader; import javax.sip.message.Request; import javax.sip.message.Response; import javax.slee.ActivityContextInterface; import javax.slee.ActivityEndEvent; import javax.slee.InitialEventSelector; import javax.slee.RolledBackContext; import javax.slee.SbbContext; import javax.slee.facilities.TimerEvent; import javax.slee.facilities.TimerFacility; import javax.slee.facilities.TimerOptions; import javax.slee.nullactivity.NullActivity; import javax.slee.nullactivity.NullActivityContextInterfaceFactory; import javax.slee.nullactivity.NullActivityFactory; import javax.slee.serviceactivity.ServiceActivity; import javax.slee.serviceactivity.ServiceActivityFactory; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import net.java.slee.resource.diameter.base.AuthSessionState; import net.java.slee.resource.diameter.base.events.avp.DiameterAvp; import net.java.slee.resource.diameter.base.events.avp.DiameterIdentity; import net.java.slee.resource.diameter.base.events.avp.GroupedAvp; import net.java.slee.resource.diameter.sh.client.DiameterShAvpFactory; import net.java.slee.resource.diameter.sh.client.MessageFactory; import net.java.slee.resource.diameter.sh.client.ShClientActivityContextInterfaceFactory; import net.java.slee.resource.diameter.sh.client.ShClientMessageFactory; import net.java.slee.resource.diameter.sh.client.ShClientProvider; import net.java.slee.resource.diameter.sh.client.ShClientSubscriptionActivity; import net.java.slee.resource.diameter.sh.client.events.PushNotificationRequest; import net.java.slee.resource.diameter.sh.client.events.avp.DiameterShAvpCodes; import net.java.slee.resource.diameter.sh.client.events.avp.UserIdentityAvp; import net.java.slee.resource.diameter.sh.server.events.PushNotificationAnswer; import net.java.slee.resource.diameter.sh.server.events.SubscribeNotificationsRequest; import net.java.slee.resource.sip.SleeSipProvider; import org.apache.log4j.Logger; import org.jdiameter.api.Avp; import org.jdiameter.api.ResultCode; import org.w3c.dom.Document; import org.xml.sax.InputSource; /** * * DiameterOpenIMSExampleSbb.java * * <br>Super project: mobicents * <br>10:58:03 PM Dec 19, 2008 * <br> * @author <a href = "mailto:baranowb@gmail.com"> Bartosz Baranowski </a> * @author <a href = "mailto:brainslog@gmail.com"> Alexandre Mendonca </a> */ public abstract class DiameterOpenIMSExampleSbb implements javax.slee.Sbb { private static Logger logger = Logger.getLogger( DiameterOpenIMSExampleSbb.class ); private SbbContext sbbContext = null; // This SBB's context private Context myEnv = null; // This SBB's environment private ShClientProvider provider = null; private ShClientMessageFactory shMessageFactory = null; private DiameterShAvpFactory avpFactory = null; private ShClientActivityContextInterfaceFactory acif = null; private TimerFacility timerFacility = null; private String originIP = "127.0.0.1"; private String originPort = "1812"; private String originRealm = "mobicents.org"; private String destinationIP = "127.0.0.1"; private String destinationPort = "3868"; private String destinationRealm = "mobicents.org"; private NullActivityFactory nullActivityFactory; private NullActivityContextInterfaceFactory nullACIFactory; private static HashMap<String, Collection<MissedCall>> missedCalls = new HashMap<String, Collection<MissedCall>>(); // SIP Stuff private AddressFactory sipAddressFactory; private HeaderFactory sipHeaderFactory; private javax.sip.message.MessageFactory sipMessageFactory; private SleeSipProvider sipProvider; public void setSbbContext( SbbContext context ) { this.sbbContext = context; try { myEnv = (Context) new InitialContext().lookup( "java:comp/env" ); // Diameter Sh Stuff provider = (ShClientProvider) myEnv.lookup("slee/resources/diameter-sh-client-ra-interface"); shMessageFactory = provider.getClientMessageFactory(); avpFactory = provider.getClientAvpFactory(); acif = (ShClientActivityContextInterfaceFactory) myEnv.lookup("slee/resources/JDiameterShClientResourceAdaptor/java.net/0.8.1/acif"); // SIP Stuff sipProvider = (SleeSipProvider) myEnv.lookup("slee/resources/jainsip/1.2/provider"); sipAddressFactory = sipProvider.getAddressFactory(); sipHeaderFactory = sipProvider.getHeaderFactory(); sipMessageFactory = sipProvider.getMessageFactory(); // SLEE Facilities timerFacility = (TimerFacility) myEnv.lookup("slee/facilities/timer"); nullActivityFactory = (NullActivityFactory)myEnv.lookup("slee/nullactivity/factory"); nullACIFactory = (NullActivityContextInterfaceFactory)myEnv.lookup("slee/nullactivity/activitycontextinterfacefactory"); } catch ( Exception e ) { logger.error( "Unable to set sbb context.", e ); } } public void unsetSbbContext() { logger.info( "unsetSbbContext invoked." ); this.sbbContext = null; } public void sbbCreate() throws javax.slee.CreateException { logger.info( "sbbCreate invoked." ); } public void sbbPostCreate() throws javax.slee.CreateException { logger.info( "sbbPostCreate invoked." ); } public void sbbActivate() { logger.info( "sbbActivate invoked." ); } public void sbbPassivate() { logger.info( "sbbPassivate invoked." ); } public void sbbRemove() { logger.info( "sbbRemove invoked." ); } public void sbbLoad() { logger.info( "sbbLoad invoked." ); } public void sbbStore() { logger.info( "sbbStore invoked." ); } public void sbbExceptionThrown( Exception exception, Object event, ActivityContextInterface activity ) { logger.info( "sbbRolledBack invoked." ); } public void sbbRolledBack( RolledBackContext context ) { logger.info( "sbbRolledBack invoked." ); } protected SbbContext getSbbContext() { logger.info( "getSbbContext invoked." ); return sbbContext; } /** * Generate a custom convergence name so that related events (with the same call * identifier, or session) will go to the same root SBB entity. */ public InitialEventSelector myInitialEventSelector(InitialEventSelector ies) { Object event = ies.getEvent(); if (event instanceof ResponseEvent) { Response response = ((ResponseEvent)event).getResponse(); if(response.getStatusCode() == 404) { ies.setCustomName( "OpenIMS-Example-StaticCustomName" ); } else { ies.setInitialEvent( false ); } } else if(event instanceof PushNotificationRequest) { ies.setCustomName( "OpenIMS-Example-StaticCustomName" ); } else { ies.setInitialEvent( false ); } return ies; } // ########################################################################## // ## EVENT HANDLERS ## // ########################################################################## public void onServiceStartedEvent( javax.slee.serviceactivity.ServiceStartedEvent event, ActivityContextInterface aci ) { try { // check if it's my service that is starting ServiceActivity sa = ( (ServiceActivityFactory) myEnv.lookup( "slee/serviceactivity/factory" ) ).getActivity(); if( sa.equals( aci.getActivity() ) ) { logger.info( "################################################################################" ); logger.info( "### O P E N I M S E X A M P L E A P P L I C A T I O N :: S T A R T E D ##" ); logger.info( "################################################################################" ); shMessageFactory = provider.getClientMessageFactory(); avpFactory = provider.getClientAvpFactory(); logger.info( "Performing sanity check..." ); logger.info( "Provider [" + provider + "]" ); logger.info( "Message Factory [" + shMessageFactory + "]" ); logger.info( "AVP Factory [" + avpFactory + "]" ); logger.info( "Check completed. Result: " + ((provider != null ? 1 : 0) + (shMessageFactory != null ? 1 : 0) + (avpFactory != null ? 1 : 0)) + "/3" ); logger.info( "Connected to " + provider.getPeerCount() + " peers." ); for(DiameterIdentity peer : provider.getConnectedPeers()) { logger.info( "Connected to Peer[" + peer.toString() + "]" ); } NullActivity timerBus = this.nullActivityFactory.createNullActivity(); ActivityContextInterface timerBusACI = this.nullACIFactory.getActivityContextInterface(timerBus); timerBusACI.attach( sbbContext.getSbbLocalObject() ); TimerOptions options = new TimerOptions(); timerFacility.setTimer(timerBusACI, null, System.currentTimeMillis() + 5000, options); } } catch ( Exception e ) { logger.error( "Unable to handle service started event...", e ); } } public void onTimerEvent(TimerEvent event, ActivityContextInterface aci) { try { Properties props = new Properties(); props.load( this.getClass().getClassLoader().getResourceAsStream("example.properties") ); this.originIP = props.getProperty( "origin.ip" ) == null ? this.originIP : props.getProperty( "origin.ip" ); this.originPort = props.getProperty( "origin.port" ) == null ? this.originPort : props.getProperty( "origin.port" ); this.originRealm = props.getProperty( "origin.realm" ) == null ? this.originRealm : props.getProperty( "origin.realm" ); this.destinationIP = props.getProperty( "destination.ip" ) == null ? this.destinationIP : props.getProperty( "destination.ip" ); this.destinationPort = props.getProperty( "destination.port" ) == null ? this.destinationPort : props.getProperty( "destination.port" ); this.destinationRealm = props.getProperty( "destination.realm" ) == null ? this.destinationRealm : props.getProperty( "destination.realm" ); String usersStr = props.getProperty( "users" ); if(usersStr != null && usersStr.length() > 0) { String[] users = usersStr.split( "," ); logger.info( "Subscribing to Profile Updates from Users " + Arrays.deepToString(users) ); for(String user : users) { sendSubscribeNotificationsRequest(user.trim()); } } else { logger.warn( "No Users are defined for the example. Nothing will happen..." ); } } catch (Exception e) { logger.error( "Failure reading properties file.", e ); } } public void onPushNotificationRequest(net.java.slee.resource.diameter.sh.client.events.PushNotificationRequest pnr, ActivityContextInterface aci) { logger.info( "Push-Notification-Request received.\r\n" + pnr ); String userData = pnr.getUserData(); try { String userId = null; DiameterAvp[] avps = pnr.getAvps(); UserIdentityAvp userIdentity = pnr.getUserIdentity(); if(userIdentity == null) { throw new IllegalStateException("No user identity in message: "+pnr); } userId = userIdentity.getPublicIdentity(); ShClientSubscriptionActivity activity = (ShClientSubscriptionActivity) aci.getActivity(); PushNotificationAnswer pna = activity.createPushNotificationAnswer(2001, false); logger.info("About to send PNA: "+pna); activity.sendPushNotificationAnswer(pna); Collection<MissedCall> mCs = missedCalls.get( userId ); if(mCs != null && mCs.size() > 0) { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = (Document) builder.parse(new InputSource(new StringReader(userData))); String userState = doc.getElementsByTagName("IMSUserState").item(0).getTextContent(); if(userState.equals("1")) { synchronized ( mCs ) { for(MissedCall missedCall : mCs ) { // Send SIP Message sendSIPMessage( userId, missedCall.getNotification() ); } } // Clear the missed calls for this user mCs.clear(); } } } catch (Exception e) { logger.error( "Error parsing User-Data AVP.", e ); } } public void on4xxResponse(javax.sip.ResponseEvent event, ActivityContextInterface aci) { Response response = event.getResponse(); logger.info( "Received SIP 4xx � " + response.getStatusCode() ); // Is it a 404? if(response.getStatusCode() == 404) { // Let's see from whom to whom String to = ((ToHeader) response.getHeader("To")).getAddress().toString(); String from = ((FromHeader) response.getHeader("From")).getAddress().toString(); logger.info( "From[" + from + "], To [" + to + "]"); String toAddress = to.substring( to.indexOf("sip:"), to.indexOf( ">" ) ); // Create the MissedCall object MissedCall mC = new MissedCall(from, new Date()); Collection<MissedCall> mCs = missedCalls.get(toAddress); if(mCs == null) { mCs = new ArrayList<MissedCall>(); missedCalls.put( toAddress, mCs ); } if(!mCs.contains( mC )) { mCs.add( mC ); } } } // ################################### public void onSubscriptionNotificationsAnswer(net.java.slee.resource.diameter.sh.client.events.SubscribeNotificationsAnswer sna, ActivityContextInterface aci) { logger.info( "Subscription-Notifications-Answer received with Result-Code[" + sna.getResultCode() + "].."); if(sna.getResultCode() != ResultCode.SUCCESS) { logger.warn( "Subscription WAS NOT successful. Please check your permissions and/or users." ); } } public void onActivityEndEvent(ActivityEndEvent event, ActivityContextInterface aci) { logger.info( " Activity Ended[" + aci.getActivity() + "]" ); } // ########################################################################## // ## PRIVATE METHODS ## // ########################################################################## private void sendSubscribeNotificationsRequest(String user) { try { ShClientSubscriptionActivity shClientSubscriptionActivity = this.provider.createShClientSubscriptionActivity(); shClientSubscriptionActivity.getDiameterAvpFactory(); shClientSubscriptionActivity.getDiameterMessageFactory(); ActivityContextInterface localACI = this.acif.getActivityContextInterface(shClientSubscriptionActivity); localACI.attach(getSbbContext().getSbbLocalObject()); List<DiameterAvp> avps = new ArrayList<DiameterAvp>(); SubscribeNotificationsRequest snr = ((ShClientMessageFactory)shClientSubscriptionActivity.getDiameterMessageFactory()).createSubscribeNotificationsRequest(); /* lbarreiro: Not needed anymore //< Subscribe-Notifications-Request > :: = < Diameter Header: 308, REQ, PXY, 16777217 > // < Session-Id > avps.add(avpFactory.getBaseFactory().createAvp(Avp.SESSION_ID, shClientSubscriptionActivity.getSessionId().getBytes() )); // { Vendor-Specific-Application-Id } DiameterAvp avpVendorId = avpFactory.getBaseFactory().createAvp( Avp.VENDOR_ID, MessageFactory._SH_VENDOR_ID ); DiameterAvp avpAcctApplicationId = avpFactory.getBaseFactory().createAvp( Avp.AUTH_APPLICATION_ID, MessageFactory._SH_APP_ID ); avps.add( avpFactory.getBaseFactory().createAvp( Avp.VENDOR_SPECIFIC_APPLICATION_ID, new DiameterAvp[]{avpVendorId, avpAcctApplicationId} ) ); // { Auth-Session-State } // { Origin-Host } avps.add(avpFactory.getBaseFactory().createAvp(Avp.ORIGIN_HOST, ("aaa://" + this.originIP + ":" + this.originPort).getBytes() )); // { Origin-Realm } avps.add(avpFactory.getBaseFactory().createAvp(Avp.ORIGIN_REALM, this.originRealm.getBytes() )); */ // [ Destination-Host ] avps.add(avpFactory.getBaseFactory().createAvp(Avp.DESTINATION_HOST, ("aaa://" + this.destinationIP + ":" + this.destinationPort).getBytes() )); // { Destination-Realm } avps.add(avpFactory.getBaseFactory().createAvp(Avp.DESTINATION_REALM, this.destinationRealm.getBytes() )); // *[ Supported-Features ] // { User-Identity } UserIdentityAvp ui = avpFactory.createUserIdentity(); ui.setPublicIdentity("sip:" + user.replaceFirst( "sip:", "" )); avps.add(ui); // [ Wildcarded-PSI ] // [ Wildcarded-IMPU ] // *[ Service-Indication ] // [ Send-Data-Indication ] // [ Server-Name ] // { Subs-Req-Type } // 0 == Subscribe // 1 == Unsubrscribe DiameterAvp srt = avpFactory.getBaseFactory().createAvp(MessageFactory._SH_VENDOR_ID, DiameterShAvpCodes.SUBS_REQ_TYPE, 0); avps.add(srt); // *{ Data-Reference } //Its enumerated: 0 == Whole data DiameterAvp avp = avpFactory.getBaseFactory().createAvp(MessageFactory._SH_VENDOR_ID, DiameterShAvpCodes.DATA_REFERENCE, 11); avps.add(avp); // [ Identity-Set ] // [ Expiry-Time ] avps.add(avpFactory.getBaseFactory().createAvp(Avp.AUTH_SESSION_STATE, AuthSessionState.Open.ordinal())); //We can user setters, but this is faster :) snr.setExtensionAvps(avps.toArray(avps.toArray(new DiameterAvp[avps.size()]))); logger.info( "Created Subscribe-Notifications-Request:\r\n" + snr ); shClientSubscriptionActivity.sendSubscriptionNotificationRequest(snr); logger.info("Subscribe-Notifications-Request sent!"); } catch (Exception e) { logger.error( "Failure trying to create/send Subscribe-Notifications-Request.", e ); } } private void sendSIPMessage(String toAddressString, String message) { try { // Create To Header Address toAddress = sipAddressFactory.createAddress(toAddressString); toAddress.setDisplayName(toAddressString); ToHeader toHeader = sipHeaderFactory.createToHeader( toAddress, null ); // Create From Header SipURI fromAddress = sipAddressFactory.createSipURI("missed-calls", System.getProperty("bind.address", "127.0.0.1")); Address fromNameAddress = sipAddressFactory.createAddress(fromAddress); fromNameAddress.setDisplayName("Missed Calls"); FromHeader fromHeader = sipHeaderFactory.createFromHeader(fromNameAddress, "12345SomeTagID6789"); // Create Via Headers ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(); ViaHeader viaHeader = sipHeaderFactory.createViaHeader( sipProvider.getListeningPoints()[0].getIPAddress(), sipProvider.getListeningPoints()[0].getPort(), sipProvider.getListeningPoints()[0].getTransport(), null); viaHeaders.add(viaHeader); // Create Max-Forwards Header MaxForwardsHeader maxForwards = this.sipHeaderFactory.createMaxForwardsHeader(70); // Create the Request URI uri = sipProvider.getAddressFactory().createURI(toAddressString); Request req = sipMessageFactory.createRequest(uri, Request.MESSAGE, this.sipProvider.getNewCallId(), sipHeaderFactory.createCSeqHeader(1L, Request.MESSAGE), fromHeader, toHeader, viaHeaders, maxForwards); // Add the content with it's type ContentTypeHeader contentType = sipHeaderFactory.createContentTypeHeader("text", "plain"); req.setContent(message, contentType); // And send it! ClientTransaction ct = sipProvider.getNewClientTransaction(req); ct.sendRequest(); } catch (Exception e) { logger.error( "Failure creating SIP Message notification.", e ); } } }