package org.mobicents.slee.xdm.server.subscription; import java.io.StringReader; import java.text.ParseException; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import javax.sip.ServerTransaction; import javax.sip.header.ContentTypeHeader; import javax.sip.message.Response; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import org.apache.log4j.Logger; import org.mobicents.slee.sipevent.server.subscription.NotifyContent; import org.mobicents.slee.sipevent.server.subscription.pojo.Subscription; import org.mobicents.slee.sipevent.server.subscription.pojo.SubscriptionKey; import org.mobicents.slee.xdm.server.ServerConfiguration; import org.mobicents.slee.xdm.server.XDMClientControlSbbLocalObject; import org.openxdm.xcap.client.appusage.resourcelists.jaxb.EntryType; import org.openxdm.xcap.client.appusage.resourcelists.jaxb.ListType; import org.openxdm.xcap.client.appusage.resourcelists.jaxb.ResourceLists; import org.openxdm.xcap.common.datasource.Document; import org.openxdm.xcap.common.error.InternalServerErrorException; import org.openxdm.xcap.common.uri.DocumentSelector; import org.openxdm.xcap.common.uri.NodeSelector; import org.openxdm.xcap.common.uri.Parser; import org.openxdm.xcap.common.uri.ResourceSelector; import org.openxdm.xcap.common.uri.TerminalSelector; import org.openxdm.xcap.common.xcapdiff.DocumentType; import org.openxdm.xcap.common.xcapdiff.ObjectFactory; import org.openxdm.xcap.common.xcapdiff.XcapDiff; /** * Logic for {@link XcapDiffSubscriptionControlSbb} * * @author martins * */ public class XcapDiffSubscriptionControl { private static final String[] xcapDiffEventPackages = { "xcap-diff" }; private XcapDiffSubscriptionControlSbbLocalObject sbb; public XcapDiffSubscriptionControl( XcapDiffSubscriptionControlSbbLocalObject sbb) { this.sbb = sbb; } public static String[] getEventPackages() { return xcapDiffEventPackages; } private ContentTypeHeader xcapDiffContentTypeHeader = null; public ContentTypeHeader getXcapDiffContentTypeHeader() { if (xcapDiffContentTypeHeader == null) { try { xcapDiffContentTypeHeader = sbb .getHeaderFactory() .createContentTypeHeader("application", "xcap-diff+xml"); } catch (ParseException e) { // ignore e.printStackTrace(); } } return xcapDiffContentTypeHeader; } public void isSubscriberAuthorized(String subscriber, String subscriberDisplayName, String notifier, SubscriptionKey key, int expires, String content, String contentType, String contentSubtype, boolean eventList, ServerTransaction serverTransaction) { StringReader stringReader = null; try { stringReader = new StringReader(content); ResourceLists object = (ResourceLists) sbb.getUnmarshaller() .unmarshal(stringReader); stringReader.close(); stringReader = null; // ok, resource-lists parsed, let's process it's lists elements HashSet<String> appUsagesToSubscribe = new HashSet<String>(); HashSet<DocumentSelector> documentsToSubscribe = new HashSet<DocumentSelector>(); for (ListType listType : object.getList()) { for (Object listOrExternalOrEntry : listType .getListOrExternalOrEntry()) { if (listOrExternalOrEntry instanceof JAXBElement) { JAXBElement jAXBElement = (JAXBElement) listOrExternalOrEntry; if (jAXBElement.getValue() instanceof EntryType) { EntryType entryType = (EntryType) jAXBElement .getValue(); // process it ResourceSelector resourceSelector = null; try { int queryComponentSeparator = entryType .getUri().indexOf('?'); if (queryComponentSeparator > 0) { resourceSelector = Parser .parseResourceSelector( ServerConfiguration.XCAP_ROOT, entryType .getUri() .substring(0, queryComponentSeparator), entryType .getUri() .substring( queryComponentSeparator + 1)); } else { resourceSelector = Parser .parseResourceSelector( ServerConfiguration.XCAP_ROOT, entryType.getUri(), null); } if (resourceSelector.getDocumentSelector() .indexOf('/') < 0) { // trying to subscribe app usage String auid = resourceSelector .getDocumentSelector(); if (logger.isInfoEnabled()) { logger.info("subscribing auid " + auid); } appUsagesToSubscribe.add(auid); } else { // trying to subscribe a document or part of // it DocumentSelector documentSelector = Parser .parseDocumentSelector(resourceSelector .getDocumentSelector()); NodeSelector nodeSelector = null; TerminalSelector terminalSelector = null; if (resourceSelector.getNodeSelector() != null) { nodeSelector = Parser .parseNodeSelector(resourceSelector .getNodeSelector()); if (nodeSelector.getTerminalSelector() != null) { // parse terminal selector terminalSelector = Parser .parseTerminalSelector(nodeSelector .getTerminalSelector()); } } if (logger.isInfoEnabled()) { logger .info("subscribing document (or part of it) " + documentSelector); } documentsToSubscribe.add(documentSelector); } } catch (Exception e) { logger .error( "failed to parse entry uri to subscribe", e); } } } } } // create subscriptions object Subscriptions subscriptions = new Subscriptions(key, appUsagesToSubscribe, documentsToSubscribe); // get subscriptions map cmp Map subscriptionsMap = sbb.getSubscriptionsMap(); if (subscriptionsMap == null) { subscriptionsMap = new HashMap(); } // build set of other documents and app usages already subscribed by // this entity HashSet<DocumentSelector> documentSelectorsAlreadySubscribed = new HashSet<DocumentSelector>(); HashSet<String> appUsagesAlreadySubscribed = new HashSet<String>(); for (Iterator i = subscriptionsMap.values().iterator(); i.hasNext();) { Subscriptions s = (Subscriptions) i.next(); for (DocumentSelector ds : s.getDocumentSelectors()) { documentSelectorsAlreadySubscribed.add(ds); } for (String auid : s.getAppUsages()) { appUsagesAlreadySubscribed.add(auid); } } // save subscriptions object on cmp subscriptionsMap.put(key, subscriptions); sbb.setSubscriptionsMap(subscriptionsMap); // let's subscribe all documents and/or app usages XDMClientControlSbbLocalObject xdm = sbb.getXDMClientControlSbb(); for (DocumentSelector documentSelector : documentsToSubscribe) { if (!documentSelectorsAlreadySubscribed .contains(documentSelector) && !appUsagesAlreadySubscribed .contains(documentSelector.getAUID())) { // app usages already subscribed does not match this // document selector's app usage, // and this document selector is not subscribed already due // to another // subscription in the same entity, so subscribe the doc xdm.subscribeDocument(documentSelector); } } for (String auid : appUsagesToSubscribe) { if (!appUsagesAlreadySubscribed.contains(auid)) { // app usages already subscribed does not match this app // usage, // so subscribe it xdm.subscribeAppUsage(auid); } } // continue new subscription process sbb.getParentSbbCMP().newSubscriptionAuthorization(subscriber, subscriberDisplayName, notifier, key, expires, Response.OK,eventList,serverTransaction); } catch (JAXBException e) { logger.error("failed to parse resource-lists in initial subscribe", e); if (stringReader != null) { stringReader.close(); } sbb.getParentSbbCMP().newSubscriptionAuthorization(subscriber, subscriberDisplayName, notifier, key, expires, Response.FORBIDDEN,eventList,serverTransaction); } } public void removingSubscription(Subscription subscription) { // get subscriptions map and remove subscription terminating Map subscriptionsMap = sbb.getSubscriptionsMap(); if (subscriptionsMap != null) { Subscriptions subscriptions = (Subscriptions) subscriptionsMap .remove(subscription.getKey()); // build set of other documents and app usages already subscribed by // this entity HashSet<DocumentSelector> documentSelectorsSubscribedByOthers = new HashSet<DocumentSelector>(); HashSet<String> appUsagesSubscribedByOthers = new HashSet<String>(); for (Iterator i = subscriptionsMap.values().iterator(); i.hasNext();) { Subscriptions s = (Subscriptions) i.next(); for (DocumentSelector ds : s.getDocumentSelectors()) { documentSelectorsSubscribedByOthers.add(ds); } for (String auid : s.getAppUsages()) { appUsagesSubscribedByOthers.add(auid); } } // now unsubscribe each that was subscribed only by the subscription // terminating XDMClientControlSbbLocalObject xdm = sbb.getXDMClientControlSbb(); for (DocumentSelector ds : subscriptions.getDocumentSelectors()) { if (!documentSelectorsSubscribedByOthers.contains(ds)) { // safe to unsubscribe this document xdm.unsubscribeDocument(ds); } } for (String auid : subscriptions.getAppUsages()) { if (!appUsagesSubscribedByOthers.contains(auid)) { // safe to unsubscribe this app usage xdm.unsubscribeAppUsage(auid); } } } else { logger .warn("Removing subscription but map of subscriptions is null"); } } public NotifyContent getNotifyContent(Subscription subscription) { // let's gather all content this subscription Map subscriptionsMap = sbb.getSubscriptionsMap(); Subscriptions subscriptions = (Subscriptions) subscriptionsMap .get(subscription.getKey()); if (subscriptions != null) { HashMap<DocumentSelector, String> documentEtags = new HashMap<DocumentSelector, String>(); // let's process first app usages for (String auid : subscriptions.getAppUsages()) { // get collections that exist in this app usage try { String[] appUsageCollections = sbb .getDataSourceSbbInterface().getCollections(auid); // for each one gather all documents names for (String collection : appUsageCollections) { String[] documentNames = sbb .getDataSourceSbbInterface().getDocuments(auid, collection); // grab each document for (String documentName : documentNames) { DocumentSelector documentSelector = new DocumentSelector( auid, collection, documentName); Document document = sbb.getDataSourceSbbInterface() .getDocument(documentSelector); if (document != null) { // TODO authorize inclusion of the document documentEtags.put(documentSelector, document .getETag()); } } } } catch (Exception e) { logger.error(e); } } for (DocumentSelector documentSelector : subscriptions .getDocumentSelectors()) { Document document = null; try { document = sbb.getDataSourceSbbInterface().getDocument( documentSelector); } catch (InternalServerErrorException e) { logger.error(e); } if (document != null) { // borrow app usage object from cache // TODO AppUsage appUsage = appUsageCache.borrow(auid); // get auth policy // TODO AuthorizationPolicy authorizationPolicy = // appUsage.getAuthorizationPolicy(); // TODO authorize inclusion of the document documentEtags.put(documentSelector, document.getETag()); } } // build notify content XcapDiff xcapDiff = new XcapDiff(); xcapDiff.setXcapRoot(ServerConfiguration.SCHEME_AND_AUTHORITY_URI + ServerConfiguration.XCAP_ROOT + "/"); ObjectFactory objectFactory = new ObjectFactory(); for (DocumentSelector documentSelector : documentEtags.keySet()) { DocumentType documentType = objectFactory.createDocumentType(); documentType.setSel(documentSelector.toString()); documentType.setNewEtag(documentEtags.get(documentSelector)); xcapDiff.getDocumentOrElementOrAttribute().add(documentType); } return new NotifyContent(xcapDiff, getXcapDiffContentTypeHeader()); } else { return null; } } public Object filterContentPerSubscriber(String subscriber, String notifier, String eventPackage, Object unmarshalledContent) { return unmarshalledContent; } public void documentUpdated(DocumentSelector documentSelector, String oldETag, String newETag, String documentAsString) { XcapDiff xcapDiff = null; // for all subscriptions in this entity that have interest on the // document Map subscriptionsMap = sbb.getSubscriptionsMap(); if (subscriptionsMap != null) { for (Iterator i = subscriptionsMap.values().iterator(); i.hasNext();) { Subscriptions s = (Subscriptions) i.next(); boolean doNotify = false; // document selector matches? for (DocumentSelector ds : s.getDocumentSelectors()) { if (ds.equals(documentSelector)) { doNotify = true; break; } } if (!doNotify) { // perhaps document selector has the same auid as one // subscribed? for (String auid : s.getAppUsages()) { if (auid.equals(documentSelector.getAUID())) { doNotify = true; break; } } } if (doNotify) { // TODO check if subscriber has authorization to read // document if (xcapDiff == null) { // lazy build of xcap diff xcapDiff = buildDocumentXcapDiff(documentSelector, newETag, oldETag); } // tell underlying sip event framework to notify subscriber sbb.getParentSbbCMP().notifySubscriber(s.getKey(), xcapDiff, getXcapDiffContentTypeHeader()); } } } } private XcapDiff buildDocumentXcapDiff(DocumentSelector documentSelector, String newETag, String oldETag) { // build notify content XcapDiff xcapDiff = new XcapDiff(); xcapDiff.setXcapRoot(ServerConfiguration.SCHEME_AND_AUTHORITY_URI + ServerConfiguration.XCAP_ROOT + "/"); ObjectFactory objectFactory = new ObjectFactory(); DocumentType documentType = objectFactory.createDocumentType(); documentType.setSel(documentSelector.toString()); if (oldETag == null && newETag != null) { // document created documentType.setNewEtag(newETag); } else if (oldETag != null && newETag == null) { // document deleted documentType.setPreviousEtag(oldETag); } else if (oldETag != null && newETag != null) { // document replaced documentType.setNewEtag(newETag); documentType.setPreviousEtag(oldETag); // FIXME provide patch ops content } xcapDiff.getDocumentOrElementOrAttribute().add(documentType); return xcapDiff; } private static Logger logger = Logger .getLogger(XcapDiffSubscriptionControl.class); }