package org.mobicents.slee.sipevent.server.subscription.eventlist;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Iterator;
import javax.sip.message.Response;
import javax.slee.ActivityContextInterface;
import javax.slee.ChildRelation;
import javax.slee.CreateException;
import javax.slee.RolledBackContext;
import javax.slee.Sbb;
import javax.slee.SbbContext;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import org.apache.log4j.Logger;
import org.mobicents.slee.sipevent.server.subscription.FlatListMakerParentSbbLocalObject;
import org.mobicents.slee.sipevent.server.subscription.FlatListMakerSbbLocalObject;
import org.mobicents.slee.xdm.server.ServerConfiguration;
import org.mobicents.slee.xdm.server.XDMClientControlParentSbbLocalObject;
import org.mobicents.slee.xdm.server.XDMClientControlSbbLocalObject;
import org.openxdm.xcap.client.appusage.resourcelists.jaxb.EntryRefType;
import org.openxdm.xcap.client.appusage.resourcelists.jaxb.EntryType;
import org.openxdm.xcap.client.appusage.resourcelists.jaxb.ExternalType;
import org.openxdm.xcap.client.appusage.resourcelists.jaxb.ListType;
import org.openxdm.xcap.client.appusage.rlsservices.jaxb.ServiceType;
import org.openxdm.xcap.common.key.XcapUriKey;
import org.openxdm.xcap.common.uri.DocumentSelector;
import org.openxdm.xcap.common.uri.Parser;
import org.openxdm.xcap.common.uri.ResourceSelector;
/**
*
*
* @author Eduardo Martins
*
*/
public abstract class FlatListMakerSbb implements Sbb,
FlatListMakerSbbLocalObject {
private static final Logger logger = Logger
.getLogger(FlatListMakerSbb.class);
// --- parent sbb
public abstract void setParentSbbCMP(FlatListMakerParentSbbLocalObject sbbLocalObject);
public abstract FlatListMakerParentSbbLocalObject getParentSbbCMP();
public void setParentSbb(FlatListMakerParentSbbLocalObject sbbLocalObject) {
setParentSbbCMP(sbbLocalObject);
}
// --- lists
public abstract void setFlatList(FlatList value);
public abstract FlatList getFlatList();
public abstract void setLists(ArrayList value);
public abstract ArrayList getLists();
public abstract void setCurrentListType(ListType value);
public abstract ListType getCurrentListType();
// --- sbb logic
/**
* flats a tree of {@link ListType}
*/
private ArrayList<ListType> addNestedLists(ArrayList<ListType> lists, ListType list) {
for (Iterator i=list.getListOrExternalOrEntry().iterator(); i.hasNext();) {
JAXBElement element = (JAXBElement) i.next();
if (element.getValue() instanceof ListType) {
addNestedLists(lists, (ListType)element.getValue());
i.remove();
}
}
lists.add(list);
return lists;
}
private void processList(FlatList flatList, ArrayList<ListType> lists, ListType currentListType) {
/*
* At this point, the RLS has a <list> element in its possession. The
* next step is to obtain a flat list of URIs from this element. To do
* that, it traverses the tree of elements rooted in the <list> element.
* Before traversal begins, the RLS initializes two lists: the "flat
* list", which will contain the flat list of the URI after traversal,
* and the "traversed list", which contains a list of HTTP URIs in
* <external> elements that have already been visited. Both lists are
* initially empty. Next, tree traversal begins. A server can use any
* tree-traversal ordering it likes, such as depth-first search or
* breadth-first search. The processing at each element in the tree
* depends on the name of the element:
*/
for (Iterator i=currentListType.getListOrExternalOrEntry().iterator(); i.hasNext();) {
JAXBElement element = (JAXBElement) i.next();
// we remove it before processing, so we never get it again
i.remove();
if (element.getValue() instanceof EntryType) {
/* o If the element is <entry>, the URI in the "uri" attribute of the
* element is added to the flat list if it is not already present (based
* on case-sensitive string equality) in that list, and the URI scheme
* represents one that can be used to service subscriptions, such as SIP
* [4] and pres [15].
*/
flatList.putEntry((EntryType) element.getValue());
}
else if (element.getValue() instanceof EntryRefType) {
/* o If the element is an <entry-ref>, the relative path reference
* making up the value of the "ref" attribute is resolved into an
* absolute URI. This is done using the procedures defined in Section
* 5.2 of RFC 3986 [7], using the XCAP root of the RLS services document
* as the base URI. This absolute URI is resolved. If the result is not
* a 200 OK containing a <entry> element, the SUBSCRIBE request SHOULD
* be rejected with a 502 (Bad Gateway). Otherwise, the <entry> element
* returned is processed as described in the previous step.
*/
// we need to derefer the entry, which is async, so we need to store current list
setCurrentListType(currentListType);
setFlatList(flatList);
setLists(lists);
EntryRefType entryRefType = (EntryRefType) element.getValue();
String resourceList = entryRefType.getRef();
if (!dereferenceResourceList(resourceList, flatList,false)) {
return;
}
}
else if (element.getValue() instanceof ExternalType) {
/* o If the element is an <external> element, the absolute URI making up
* the value of the "anchor" attribute of the element is examined. If
* the URI is on the traversed list, the server MUST cease traversing
* the tree, and SHOULD reject the SUBSCRIBE request with a 502 (Bad
* Gateway). If the URI is not on the traversed list, the server adds
* the URI to the traversed list, and dereferences the URI. If the
* result is not a 200 OK containing a <list> element, the SUBSCRIBE
* request SHOULD be rejected with a 502 (Bad Gateway). Otherwise, the
* RLS replaces the <external> element in its local copy of the tree
* with the <list> element that was returned, and tree traversal
* continues.
*
*
* Because the <external> element is used to dynamically construct the
* tree, there is a possibility of recursive evaluation of references.
* The traversed list is used to prevent this from happening.
*
* Once the tree has been traversed, the RLS can create virtual
* subscriptions to each URI in the flat list, as defined in [14]. In
* the processing steps outlined above, when an <entry-ref> or
* <external> element contains a reference that cannot be resolved,
* failing the request is at SHOULD strength. In some cases, an RLS may
* provide better service by creating virtual subscriptions to the URIs
* in the flat list that could be obtained, omitting those that could
* not. Only in those cases should the SHOULD recommendation be ignored.
*
*
*/
//FIXME add support to really external uris
ExternalType externalType = (ExternalType)element.getValue();
String resourceList = externalType.getAnchor();
// we need to derefer the entry, which is async, so we need to store current list
setCurrentListType(currentListType);
setFlatList(flatList);
setLists(lists);
if (!dereferenceResourceList(resourceList, flatList,true)) {
return;
}
}
}
// if we get here this list is fully processed, so move to the next one
if (!lists.isEmpty()) {
ListType nextListType = lists.remove(lists.size()-1);
processList(flatList, lists, nextListType);
}
else {
returnFlatListToParent(flatList);
}
}
/**
*
* @param resourceList
* @param flatList
* @param absoluteURI indicates if the uri is absolute or not
* @return true if the make of the flat list should continue, false otherwise
*/
private boolean dereferenceResourceList(String resourceList, FlatList flatList, boolean absoluteURI) {
if (logger.isDebugEnabled()) {
logger.debug("Dereferencing resource list "+resourceList);
}
XcapUriKey key = null;
DocumentSelector documentSelector = null;
try {
if (absoluteURI) {
String shemeAndAuthorityURI = getSchemeAndAuthorityURI();
if (resourceList.startsWith(shemeAndAuthorityURI)) {
resourceList = resourceList.substring(shemeAndAuthorityURI.length());
}
else {
if (logger.isDebugEnabled()) {
logger.debug("The resource list (to dereference) uri "+resourceList+" does not starts with server scheme and authority uri "+shemeAndAuthorityURI);
}
return true;
}
}
else {
resourceList = "/" + resourceList;
}
ResourceSelector resourceSelector = null;
int queryComponentSeparator = resourceList.indexOf('?');
if (queryComponentSeparator > 0) {
resourceSelector = Parser
.parseResourceSelector(
getLocalXcapRoot(),
resourceList
.substring(0,
queryComponentSeparator),
resourceList
.substring(
queryComponentSeparator + 1));
} else {
resourceSelector = Parser
.parseResourceSelector(
getLocalXcapRoot(),
resourceList, null);
}
documentSelector = Parser.parseDocumentSelector(resourceSelector.getDocumentSelector());
if (!documentSelector.getAUID().equals("resource-lists")) {
logger.error("Unable to make flat list, invalid or not supported resource list uri: "+resourceList);
flatList.setStatus(Response.BAD_GATEWAY);
returnFlatListToParent(flatList);
return false;
}
else {
flatList.getResourceLists().add(documentSelector);
setFlatList(flatList);
}
key = new XcapUriKey(resourceSelector);
}
catch (Exception e) {
logger.error("Failed to parse resource list (to dereference) "+resourceList,e);
flatList.setStatus(Response.BAD_GATEWAY);
returnFlatListToParent(flatList);
return false;
}
XDMClientControlSbbLocalObject xdmClientSbb = getXDMClientControlSbb();
xdmClientSbb.get(key,null);
return false;
}
private void makeFlatList(FlatList flatList, ListType listType) {
// the specs don't refer it but a list can contain inner lists, so lets
// build a queue of lists to process
ArrayList<ListType> lists = addNestedLists(new ArrayList<ListType>(), listType);
ListType currentListType = lists.remove(lists.size()-1);
// kick off the final process
processList(flatList, lists, currentListType);
}
public void makeFlatList(ServiceType serviceType) {
// create flat list and store it in cmp
FlatList flatList = new FlatList(serviceType);
/*
*
* If the <service> element had a <list> element, it is extracted. If
* the <service> element had a <resource-list> element, its URI content
* is dereferenced. The result should be a <list> element. If it is not,
* the request SHOULD be rejected with a 502 (Bad Gateway). Otherwise,
* that <list> element is extracted.
*
*/
ListType listType = serviceType.getList();
if (listType != null) {
makeFlatList(flatList, listType);
}
else {
String resourceList = serviceType.getResourceList().trim();
setFlatList(flatList);
dereferenceResourceList(resourceList, flatList,true);
}
}
// --- XDM call backs
public void getResponse(XcapUriKey key, int responseCode, String mimetype,
String content, String tag) {
FlatList flatList = getFlatList();
ArrayList lists = getLists();
ListType currentListType = getCurrentListType();
if (responseCode == 200) {
// unmarshall content
Object o = null;
StringReader stringReader = new StringReader(content);
try {
o = jaxbContext.createUnmarshaller().unmarshal(stringReader);
} catch (JAXBException e) {
logger.error("failed to unmarshall content for key "+key,e);
// if it was deferring an entry ref continue
flatList.setStatus(Response.BAD_GATEWAY);
if (currentListType != null) {
processList(flatList, lists, currentListType);
}
else {
returnFlatListToParent(flatList);
}
return;
}
finally {
stringReader.close();
}
// check what type of object we got
if (o instanceof ListType) {
// we are deferring a resource list
if (lists == null) {
makeFlatList(flatList,(ListType) o);
}
else {
// restart procedure to make flat list
processList(flatList, lists, (ListType) o);
}
}
else if (o instanceof EntryType) {
// we are deferring a entry ref
// add entry
flatList.putEntry((EntryType) o);
// restart procedure to make flat list
processList(flatList, lists, currentListType);
}
}
else {
if (logger.isInfoEnabled()) {
logger.info("xdm get request didn't returned sucess code. key: "+key);
}
flatList.setStatus(Response.BAD_GATEWAY);
if (getCurrentListType() != null) {
processList(flatList, lists, currentListType);
}
else {
returnFlatListToParent(flatList);
}
}
}
private void returnFlatListToParent(FlatList flatList) {
getParentSbbCMP().flatListMade(flatList);
sbbContext.getSbbLocalObject().remove();
}
// --- aux public getter that reuires jboss running, publi so junit tests overwrite it
public String getLocalXcapRoot() {
return ServerConfiguration.XCAP_ROOT;
}
public String getSchemeAndAuthorityURI() {
return ServerConfiguration.SCHEME_AND_AUTHORITY_URI;
}
// --- XDM CLIENT CHILD SBB
public abstract ChildRelation getXDMClientControlChildRelation();
public abstract XDMClientControlSbbLocalObject getXDMClientControlChildSbbCMP();
public abstract void setXDMClientControlChildSbbCMP(
XDMClientControlSbbLocalObject value);
public XDMClientControlSbbLocalObject getXDMClientControlSbb() {
XDMClientControlSbbLocalObject childSbb = getXDMClientControlChildSbbCMP();
if (childSbb == null) {
try {
childSbb = (XDMClientControlSbbLocalObject) getXDMClientControlChildRelation()
.create();
} catch (Exception e) {
logger.error("Failed to create child sbb", e);
return null;
}
setXDMClientControlChildSbbCMP(childSbb);
childSbb
.setParentSbb((XDMClientControlParentSbbLocalObject) this.sbbContext
.getSbbLocalObject());
}
return childSbb;
}
// ---- JAXB
/*
* JAXB context is thread safe
*/
private static final JAXBContext jaxbContext = initJAXBContext();
private static JAXBContext initJAXBContext() {
try {
return JAXBContext
.newInstance("org.openxdm.xcap.client.appusage.rlsservices.jaxb"
+ ":org.openxdm.xcap.client.appusage.resourcelists.jaxb");
} catch (JAXBException e) {
logger.error("failed to create jaxb context");
return null;
}
}
// ----------- SBB OBJECT's LIFE CYCLE
private SbbContext sbbContext;
public void setSbbContext(SbbContext sbbContext) {
this.sbbContext = sbbContext;
}
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;
}
}