package org.openxdm.xcap.server.slee.appusage.rlsservices; import java.net.URI; import java.net.URLDecoder; import java.util.HashSet; import java.util.Set; import javax.xml.transform.TransformerException; import javax.xml.validation.Validator; import org.apache.log4j.Logger; import org.openxdm.xcap.common.appusage.AppUsage; import org.openxdm.xcap.common.datasource.DataSource; import org.openxdm.xcap.common.error.ConstraintFailureConflictException; import org.openxdm.xcap.common.error.InternalServerErrorException; import org.openxdm.xcap.common.error.SchemaValidationErrorConflictException; import org.openxdm.xcap.common.error.UniquenessFailureConflictException; import org.openxdm.xcap.common.uri.DocumentSelector; import org.openxdm.xcap.common.xml.TextWriter; import org.openxdm.xcap.server.etag.ETagGenerator; import org.openxdm.xcap.server.slee.appusage.resourcelists.ResourceListsAppUsage; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class RLSServicesAppUsage extends AppUsage { public static final String ID = "rls-services"; public static final String DEFAULT_DOC_NAMESPACE = "urn:ietf:params:xml:ns:rls-services"; public static final String MIMETYPE = "application/rls-services+xml"; private static Logger logger = Logger.getLogger(RLSServicesAppUsage.class); public RLSServicesAppUsage(Validator schemaValidator) { super(ID,DEFAULT_DOC_NAMESPACE,MIMETYPE,schemaValidator,new RLSServicesAuthorizationPolicy()); } /** * (re)builds the global document on delete */ public void processResourceInterdependenciesOnDelete(Document document, DocumentSelector documentSelector, DataSource dataSource) throws SchemaValidationErrorConflictException, UniquenessFailureConflictException, InternalServerErrorException, ConstraintFailureConflictException { super.processResourceInterdependenciesOnDelete(document, documentSelector, dataSource); // declare uri set, to keep track of uniqueness // initialize service uri attrib set Set<String> serviceUriSet = new HashSet<String>(); // is this a put of an user document? int separator = documentSelector.getDocumentParent().indexOf('/'); if (separator > 0 && documentSelector.getDocumentParent().substring(0,separator).equals("users")) { // get global index doc DocumentSelector globalDocumentSelector = new DocumentSelector(ID,"global","index"); org.openxdm.xcap.common.datasource.Document dataSourceGlobalDocument = dataSource.getDocument(globalDocumentSelector); if (dataSourceGlobalDocument != null) { // let's gather all uris of service elements to delete from the global doc NodeList documentChildNodes = document.getDocumentElement().getChildNodes(); for(int i=0;i<documentChildNodes.getLength();i++) { Node documentChildNode = documentChildNodes.item(i); if (documentChildNode.getNodeType() == Node.ELEMENT_NODE && documentChildNode.getLocalName().equals("service")) { // assume doc is in good shape serviceUriSet.add(((Element) documentChildNode).getAttributeNode("uri").getNodeValue()); } } Document globalDocument = dataSourceGlobalDocument.getAsDOMDocument(); if (logger.isDebugEnabled()) { try { logger.debug("Global doc before deleting:\n "+TextWriter.toString(globalDocument)); } catch (TransformerException e1) { logger.error(e1); } } Node rlsServices = globalDocument.getDocumentElement(); Node rlsServicesChild = rlsServices.getFirstChild(); Node nextChild = null; do { nextChild = rlsServicesChild.getNextSibling(); if (rlsServicesChild.getNodeType() == Node.ELEMENT_NODE && rlsServicesChild.getLocalName().equals("service")) { if (logger.isDebugEnabled()) { logger.debug("Deleting service with uri "+((Element) rlsServicesChild).getAttributeNode("uri").getNodeValue()); } if (serviceUriSet.contains(((Element) rlsServicesChild).getAttributeNode("uri").getNodeValue())) { // remove the node rlsServices.removeChild(rlsServicesChild); } } rlsServicesChild = nextChild; } while(nextChild != null); try { dataSource.updateDocument(globalDocumentSelector, dataSourceGlobalDocument.getETag(),ETagGenerator.generate("/rls-services/global/index"), TextWriter.toString(globalDocument),globalDocument); } catch (TransformerException e) { // ignore, won't happen e.printStackTrace(); } if (logger.isDebugEnabled()) { try { logger.debug("Global doc after deleting:\n "+TextWriter.toString(globalDocument)); } catch (TransformerException e1) { logger.error(e1); } } } } } /** * (re)builds the global document on put */ public void processResourceInterdependenciesOnPut(Document document, DocumentSelector documentSelector, DataSource dataSource) throws SchemaValidationErrorConflictException, UniquenessFailureConflictException, InternalServerErrorException, ConstraintFailureConflictException { super.processResourceInterdependenciesOnPut(document, documentSelector, dataSource); /* This application usage defines an additional resource interdependence between a single document in the global tree and all documents in the user tree with the name "index". This global document is formed as the union of all of the index documents for all users within the same XCAP root. In this case, the union operation implies that each <service> element in a user document will also be present as a <service> element in the global document. The inverse is true as well. Every <service> element in the global document exists within a user document within the same XCAP root. As an example, consider the RLS services document for user sip:joe@example.com: <?xml version="1.0" encoding="UTF-8"?> <rls-services> <service uri="sip:mybuddies@example.com"> <resource-list>http://xcap.example.com/resource-lists/users/si p:joe@example.com/index/~~/resource-lists/list%5b@name=%22l1% 22%5d</resource-list> <packages> <package>presence</package> </packages> </service> </rls-services> And consider the RLS services document for user bob: <?xml version="1.0" encoding="UTF-8"?> <rls-services> <service uri="sip:marketing@example.com"> <list name="marketing"> <rl:entry uri="sip:joe@example.com"/> <rl:entry uri="sip:sudhir@example.com"/> </list> <packages> <package>presence</package> </packages> </service> </rls-services> The global document at http://xcap.example.com/rls-services/global/index would look like this: <?xml version="1.0" encoding="UTF-8"?> <rls-services xmlns="urn:ietf:params:xml:ns:rls-services" xmlns:rl="urn:ietf:params:xml:ns:resource-lists" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <service uri="sip:mybuddies@example.com"> <resource-list>http://xcap.example.com/resource-lists/user s/sip:joe@example.com/index/~~/resource-lists/list%5b@nam e=%22l1%22%5d</resource-list> <packages> <package>presence</package> </packages> </service> <service uri="sip:marketing@example.com"> <list name="marketing"> <rl:entry uri="sip:joe@example.com"/> <rl:entry uri="sip:sudhir@example.com"/> </list> <packages> <package>presence</package> </packages> </service> </rls-services> */ // declare uri set, to keep track of uniqueness // initialize service uri attrib set Set<String> serviceUriSet = new HashSet<String>(); // split document parent String[] documentParentParts = documentSelector.getDocumentParent().split("/"); // is this a put of an user document? if (documentParentParts[0].equals("users")) { // get document's element childs NodeList documentChildNodes = document.getDocumentElement().getChildNodes(); // get global index doc DocumentSelector globalDocumentSelector = new DocumentSelector(ID,"global","index"); org.openxdm.xcap.common.datasource.Document dataSourceGlobalDocument = dataSource.getDocument(globalDocumentSelector); if (dataSourceGlobalDocument == null) { // this is the first document being inserted, just verify service elements uris are unique for(int d=0;d<documentChildNodes.getLength();d++) { Node documentChildNode = documentChildNodes.item(d); if (documentChildNode.getNodeType() == Node.ELEMENT_NODE && documentChildNode.getLocalName().equals("service")) { Element element = (Element) documentChildNode; // service element Attr serviceUriAttr = element.getAttributeNode("uri"); // attr must be unique if (serviceUriSet.contains(serviceUriAttr.getNodeValue())) { // not unique, raise exception throw new UniquenessFailureConflictException(); } else { // unique so far // add it to the service uri set serviceUriSet.add(serviceUriAttr.getNodeValue()); } } } // document verified, let's define this one as the global one try { dataSource.addCollection(ID, globalDocumentSelector.getDocumentParent()); dataSource.createDocument(globalDocumentSelector, ETagGenerator.generate("/rls-services/global/index"), TextWriter.toString(document), document); } catch (TransformerException e) { // ignore, won't happen, otherwise xdm had returned error to client e.printStackTrace(); } } else { Document globalDocument = dataSourceGlobalDocument.getAsDOMDocument(); if (logger.isDebugEnabled()) { try { logger.debug("Global doc before inserting:\n "+TextWriter.toString(globalDocument)); } catch (TransformerException e1) { logger.error(e1); } } // lets process global doc and gather all service uris there NodeList globalChildNodes = globalDocument.getDocumentElement().getChildNodes(); for(int i=0;i<globalChildNodes.getLength();i++) { // assume global doc is in good shape for faster processing Node globalChildNode = globalChildNodes.item(i); if (globalChildNode.getNodeType() == Node.ELEMENT_NODE && globalChildNode.getLocalName().equals("service")) { serviceUriSet.add(((Element)globalChildNodes.item(i)).getAttributeNode("uri").getNodeValue()); } } // this may be an update, if so we need to remove the old services before checking for uniqueness org.openxdm.xcap.common.datasource.Document oldDatasourceDocument = dataSource.getDocument(documentSelector); if (oldDatasourceDocument != null) { NodeList oldDocumentChildNodes = oldDatasourceDocument.getAsDOMDocument().getDocumentElement().getChildNodes(); for(int i=0;i<oldDocumentChildNodes.getLength();i++) { Node oldDocumentChildNode = oldDocumentChildNodes.item(i); if (oldDocumentChildNode.getNodeType() == Node.ELEMENT_NODE && oldDocumentChildNode.getLocalName().equals("service")) { serviceUriSet.remove(((Element)oldDocumentChildNodes.item(i)).getAttributeNode("uri").getNodeValue()); } } } // now we process the document being inserted for(int d=0;d<documentChildNodes.getLength();d++) { Node documentChildNode = documentChildNodes.item(d); if (documentChildNode.getNodeType() == Node.ELEMENT_NODE && documentChildNode.getLocalName().equals("service")) { // service element Element documentElement = (Element) documentChildNode; Attr serviceUriAttr = documentElement.getAttributeNode("uri"); // attr must be unique if (serviceUriSet.contains(serviceUriAttr.getNodeValue())) { // not unique, raise exception throw new UniquenessFailureConflictException(); } else { // add it to the service uri set serviceUriSet.add(serviceUriAttr.getNodeValue()); // add service to the global one Node importedNode = globalDocument.importNode(documentElement,true); Node insertedNode = globalDocument.getDocumentElement().insertBefore(importedNode,null); } } } try { String xml = TextWriter.toString(globalDocument); if (logger.isDebugEnabled()) { logger.debug("Global doc being inserting:\n "+xml); } dataSource.updateDocument(globalDocumentSelector, dataSourceGlobalDocument.getETag(), ETagGenerator.generate("/rls-services/global/index"), xml,globalDocument); } catch (TransformerException e) { // ignore, won't happen, otherwise xdm had returned error to client e.printStackTrace(); } } } } public void checkConstraintsOnPut(Document document, String xcapRoot, DocumentSelector documentSelector, DataSource dataSource) throws UniquenessFailureConflictException, InternalServerErrorException, ConstraintFailureConflictException { super.checkConstraintsOnPut(document, xcapRoot, documentSelector, dataSource); /* NOTE: the contraint below is ensured when (re)building the global doc "The URI in the "uri" attribute of the <service> element MUST be unique amongst all other URIs in "uri" elements in any <service> element in any document on a particular server. This uniqueness constraint spans across XCAP roots." */ /* TODO ensure the uri is not a network resource, such as the uri of a sip user "Furthermore, the URI MUST NOT correspond to an existing resource within the domain of the URI. If a server is asked to set the URI to something that already exists, the server MUST reject the request with a 409, and use the mechanisms defined in [10] to suggest alternate URIs that have not yet been allocated." */ // get document's element childs NodeList childNodes = document.getDocumentElement().getChildNodes(); // process each one for(int i=0;i<childNodes.getLength();i++) { Node childNode = childNodes.item(i); if (childNode.getNodeType() == Node.ELEMENT_NODE && childNode.getLocalName().equals("service")) { // service element // get childs NodeList serviceChildNodes = childNode.getChildNodes(); // process each one for(int j=0;j<serviceChildNodes.getLength();j++) { Node serviceChildNode = serviceChildNodes.item(j); if (serviceChildNode.getNodeType() == Node.ELEMENT_NODE && serviceChildNode.getLocalName().equals("list")) { // list element /* o In addition, an RLS services document can contain a <list> element, which in turn can contain <entry>, <entry-ref> and <external> elements. The constraints defined for these elements in Section 3.4.7 MUST be enforced. */ ResourceListsAppUsage.checkNodeResourceListConstraints(serviceChildNode); } else if (serviceChildNode.getNodeType() == Node.ELEMENT_NODE && serviceChildNode.getLocalName().equals("resource-list")) { // resource-list element // flag setup boolean throwException = true; // node value is the uri to evaluate String resourceListUri = serviceChildNode.getTextContent().trim(); try { // build uri URI uri = new URI(resourceListUri); String uriScheme = uri.getScheme(); /* The URI in a <resource-list> element MUST be an absolute URI. */ if(uriScheme != null && (uriScheme.equalsIgnoreCase("http") || uriScheme.equalsIgnoreCase("https"))) { // split string after "scheme://" to find path segments String[] resourceListUriPaths = resourceListUri.substring(uriScheme.length()+3).split("/"); for(int k=0;k<resourceListUriPaths.length;k++) { /* The server MUST verify that the URI path contains "resource-lists" in the path segment corresponding to the AUID. */ if (resourceListUriPaths[k].equals(ResourceListsAppUsage.ID)) { // found auid if (!resourceListUriPaths[k+1].equals("global")) { // not global /* If the RLS services document is within the XCAP user tree (as opposed to the global tree), the server MUST verify that the XUI in the path is the same as the XUI in the URI of to the resource-list document. */ // decode the candidate xui first String resourceListXUIDecoded = URLDecoder.decode(resourceListUriPaths[k+2],"UTf-8"); String requestXUI = documentSelector.getDocumentParent().split("/")[1]; if (resourceListXUIDecoded.equals(requestXUI)) { throwException = false; } else { logger.error("not the same xcap user id in request ("+requestXUI+") and resource list ("+resourceListXUIDecoded+") URIs"); } break; } else { throwException = false; break; } } } } } catch (Exception e) { // ignore logger.error(e.getMessage(),e); } // throw exception if needed if (throwException) { throw new ConstraintFailureConflictException("Bad URI in resource-list element >> "+resourceListUri); } } } } } } }