package org.mobicents.slee.sippresence.server.publication;
import gov.nist.javax.sip.Utils;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import org.mobicents.slee.sippresence.pojo.commonschema.NoteT;
import org.mobicents.slee.sippresence.pojo.datamodel.Device;
import org.mobicents.slee.sippresence.pojo.datamodel.Person;
import org.mobicents.slee.sippresence.pojo.pidf.Contact;
import org.mobicents.slee.sippresence.pojo.pidf.Note;
import org.mobicents.slee.sippresence.pojo.pidf.Presence;
import org.mobicents.slee.sippresence.pojo.pidf.Status;
import org.mobicents.slee.sippresence.pojo.pidf.Tuple;
import org.mobicents.slee.sippresence.pojo.pidf.oma.ServiceDescription;
public class PresenceCompositionPolicy {
/**
*
* @param presence the new state to add to the composition
* @param otherPresence the current presence composition state
* @return
*/
public Presence compose(Presence presence, Presence otherPresence) {
Presence result = new Presence();
result.setEntity(presence.getEntity());
// process tuples
result.getTuple().addAll(composeTuples(presence.getTuple(), otherPresence.getTuple()));
// extract devices and persons from anys
List<Object> presenceAny = presence.getAny();
List<Device> presenceDevices = new ArrayList<Device>();
List<Person> presencePersons = new ArrayList<Person>();
for (Iterator<?> it = presenceAny.iterator(); it.hasNext();) {
Object obj = it.next();
if (obj instanceof Device) {
presenceDevices.add((Device)obj);
it.remove();
}
else if (obj instanceof Person) {
presencePersons.add((Person)obj);
it.remove();
}
}
List<Object> otherPresenceAny = otherPresence.getAny();
List<Device> otherPresenceDevices = new ArrayList<Device>();
List<Person> otherPresencePersons = new ArrayList<Person>();
for (Iterator<?> it = otherPresenceAny.iterator(); it.hasNext();) {
Object obj = it.next();
if (obj instanceof Device) {
otherPresenceDevices.add((Device)obj);
it.remove();
}
else if (obj instanceof Person) {
otherPresencePersons.add((Person)obj);
it.remove();
}
}
// process devices
result.getAny().addAll(composeDevices(presenceDevices, otherPresenceDevices));
// process persons
result.getAny().addAll(composePersons(presencePersons, otherPresencePersons));
// process anys
result.getAny().addAll(compose(presenceAny, otherPresenceAny,true,false,false));
// process notes
result.getNote().addAll(composeNotes(presence.getNote(), presence.getNote()));
return result;
}
private List<Tuple> composeTuples(List<Tuple> tuples,List<Tuple> otherTuples) {
ArrayList<Tuple> result = new ArrayList<Tuple>();
// process all from tuples trying to match each with othertuples
for (Iterator<Tuple> tuplesIt = tuples.iterator(); tuplesIt.hasNext(); ) {
Tuple tuple = tuplesIt.next();
for (Iterator<Tuple> otherTuplesIt = otherTuples.iterator(); otherTuplesIt.hasNext(); ) {
Tuple otherTuple = otherTuplesIt.next();
Tuple compositionTuple = compose(tuple, otherTuple);
if (compositionTuple != null) {
// the composition has a result
result.add(compositionTuple);
// remove the tuples to not be iterated again
tuplesIt.remove();
otherTuplesIt.remove();
break;
}
}
}
// now add the ones left but replacing the ids
for (Tuple tuple : tuples) {
tuple.setId(Utils.getInstance().generateTag());
result.add(tuple);
}
for (Tuple tuple : otherTuples) {
tuple.setId(Utils.getInstance().generateTag());
result.add(tuple);
}
return result;
}
private Tuple compose(Tuple tuple, Tuple otherTuple) {
Tuple result = new Tuple();
result.setId(Utils.getInstance().generateTag());
/*
*
* Service elements (defined in section 10.1.2) If the following
* conditions all apply:
*
* a. If one <tuple> element includes a <contact> element, as
* defined in [RFC3863], other <tuple> elements include an
* identical <contact> element; and
*/
if (tuple.getContact() == null) {
if (otherTuple.getContact() != null) {
// different contacts
return null;
}
// else ignore no contacts
}
else {
if (otherTuple.getContact() == null) {
// different contacts
return null;
}
else {
Contact composedContact = this.compose(tuple.getContact(), otherTuple.getContact());
if (composedContact != null) {
result.setContact(composedContact);
}
else {
return null;
}
}
}
/* b. If one <tuple> element includes a <service-description>
* element, as defined in section 10.5.1, other <tuple> elements
* include an identical <service-description> element. Two
* <service-description> elements are identical if they contain
* identical <service-id> and <version> elements; and
*
* c. If one <tuple> element includes a <class> element, as
* defined in section 10.5.1, other <tuple> elements include an
* identical <class> element; and
*
* d. If there are no conflicting elements (i.e. same elements
* with different values) or attributes under the <tuple>
* elements. Different <timestamp> values are not considered as
* a conflict.
*
* then the PS SHALL:
*
* a. Aggregate elements within a <tuple> element that are
* published from different Presence Sources into one <tuple>
* element. Identical elements with the same value and
* attributes SHALL not be duplicated; and
*
* b. Set the �priority� attribute of the <contact> element in
* the aggregated <tuple> element to the highest one among those
* in the input <tuple> elements, if any �priority� attribute is
* present; and
*
* c. Set the <timestamp> of the aggregated <tuple> to the most
* recent one among the ones that contribute to the aggregation;
* and
*
* d. Keep no more than one <description> element from the
* <service-description> elements of the aggregated <tuple>
* element when there are different values of the <description>
* elements.
*/
// process status
if (tuple.getStatus() == null) {
if (otherTuple.getStatus() != null) {
// different status
return null;
}
// else ignore no status
}
else {
if (otherTuple.getStatus() == null) {
// different status
return null;
}
else {
Status status = this.compose(tuple.getStatus(), otherTuple.getStatus());
if (status != null) {
result.setStatus(status);
}
else {
return null;
}
}
}
// process anys
List<Object> anys = compose(tuple.getAny(), otherTuple.getAny(),false,false,false);
if (anys != null) {
result.getAny().addAll(anys);
}
else {
return null;
}
// process notes
result.getNote().addAll(composeNotes(tuple.getNote(), otherTuple.getNote()));
// process timestamp
if (tuple.getTimestamp() == null) {
result.setTimestamp(otherTuple.getTimestamp());
}
else {
if (otherTuple.getTimestamp() == null) {
result.setTimestamp(tuple.getTimestamp());
}
else {
if (tuple.getTimestamp().compare(otherTuple.getTimestamp()) > 0) {
result.setTimestamp(tuple.getTimestamp());
}
else {
result.setTimestamp(otherTuple.getTimestamp());
}
}
}
return result;
}
/**
* Compose a {@link Contact} object from two non null ones.
* @param contact
* @param otherContact
* @return
*/
private Contact compose(Contact contact, Contact otherContact) {
Contact result = new Contact();
// compare values
if (contact.getValue().equals(otherContact.getValue())) {
result.setValue(contact.getValue());
}
else {
return null;
}
// process priority
if (contact.getPriority() != null) {
if (otherContact.getPriority() == null || otherContact.getPriority().compareTo(contact.getPriority()) < 0) {
result.setPriority(contact.getPriority());
}
}
if (result.getPriority() == null) {
result.setPriority(otherContact.getPriority());
}
return result;
}
private Status compose(Status status, Status otherStatus) {
final Status result = new Status();
// check basic
if (status.getBasic() != null && status.getBasic().equals(otherStatus.getBasic())) {
result.setBasic(status.getBasic());
}
else {
if (otherStatus.getBasic() != null) {
return null;
}
// else ignore
}
// lets process the anys
List<Object> anys = compose(status.getAny(), otherStatus.getAny(),false,false,false);
if (anys != null) {
result.getAny().addAll(anys);
return result;
}
else {
return null;
}
}
private List<Object> compose(List<Object> anys, List<Object> otherAnys, boolean allowConflicts, boolean keepRecentInConflict, boolean anysIsOlder) {
ArrayList<Object> result = new ArrayList<Object>();
for (Iterator<?> anysIt = anys.iterator(); anysIt.hasNext(); ) {
Object anysObject = anysIt.next();
for (Iterator<?> otherAnysIt = otherAnys.iterator(); otherAnysIt.hasNext(); ) {
Object otherAnysObject = otherAnysIt.next();
if (anysObject instanceof JAXBElement) {
JAXBElement<?> anysElement = (JAXBElement<?>) anysObject;
if (otherAnysObject instanceof JAXBElement) {
JAXBElement<?> otherAnysElement = (JAXBElement<?>) otherAnysObject;
if (anysElement.getName().equals(otherAnysElement.getName())) {
// same element type, check for conflict
if (isConflict(anysElement.getValue(), otherAnysElement.getValue())) {
if (allowConflicts) {
if (keepRecentInConflict) {
if (anysIsOlder) {
result.add(otherAnysElement);
}
else {
result.add(anysElement);
}
anysIt.remove();
otherAnysIt.remove();
break;
}
}
else {
return null;
}
}
}
}
}
else {
if (anysObject.getClass() == otherAnysObject.getClass()) {
// same element type, check for conflict
if (isConflict(anysObject, otherAnysObject)) {
if (allowConflicts) {
if (keepRecentInConflict) {
if (anysIsOlder) {
result.add(otherAnysObject);
}
else {
result.add(anysObject);
}
anysIt.remove();
otherAnysIt.remove();
break;
}
}
else {
return null;
}
}
else {
Object composedObject = compose(anysObject, otherAnysObject);
if (composedObject != null) {
result.add(composedObject);
anysIt.remove();
otherAnysIt.remove();
break;
}
}
}
}
}
}
// now add the ones left
result.addAll(anys);
result.addAll(otherAnys);
return result;
}
/**
* Here we compose 2 objects of same class
* @param anysObject
* @param otherAnysObject
* @return
*/
private Object compose(Object anysObject, Object otherAnysObject) {
if (anysObject instanceof ServiceDescription) {
ServiceDescription anysServiceDescription = (ServiceDescription) anysObject;
ServiceDescription otherAnysServiceDescription = (ServiceDescription) otherAnysObject;
ServiceDescription result = new ServiceDescription();
result.setServiceId(anysServiceDescription.getServiceId());
result.setVersion(anysServiceDescription.getVersion());
result.setDescription(anysServiceDescription.getDescription());
result.getAny().addAll(compose(anysServiceDescription.getAny(), otherAnysServiceDescription.getAny(), true, false, false));
// merge other attributes, leaving
for (QName attributeName : anysServiceDescription.getOtherAttributes().keySet()) {
result.getOtherAttributes().put(attributeName, anysServiceDescription.getOtherAttributes().get(attributeName));
}
for (QName attributeName : otherAnysServiceDescription.getOtherAttributes().keySet()) {
String attributeValue = result.getOtherAttributes().get(attributeName);
if (attributeValue != null) {
if (!attributeValue.equals(otherAnysServiceDescription.getOtherAttributes().get(attributeName))) {
// attribute conflict
result.getOtherAttributes().remove(attributeName);
}
}
else {
result.getOtherAttributes().put(attributeName, otherAnysServiceDescription.getOtherAttributes().get(attributeName));
}
}
return result;
}
else {
return null;
}
}
private boolean isConflict(Object object, Object otherObject) {
// well known objects
if (object.getClass() == ServiceDescription.class) {
ServiceDescription serviceDescription = (ServiceDescription) object;
ServiceDescription otherServiceDescription = (ServiceDescription) object;
if (serviceDescription.getServiceId() == null) {
if (otherServiceDescription.getServiceId() != null)
return true;
} else if (!serviceDescription.getServiceId().equals(otherServiceDescription.getServiceId()))
return true;
if (serviceDescription.getVersion() == null) {
if (otherServiceDescription.getVersion() != null)
return true;
} else if (!serviceDescription.getVersion().equals(otherServiceDescription.getVersion()))
return true;
return false;
}
// unknown
else if (object.equals(otherObject)) {
return false;
}
else {
return true;
}
}
private List<Note> composeNotes(List<Note> notes,List<Note> otherNotes) {
ArrayList<Note> result = new ArrayList<Note>();
// process all from notes trying to match each with otherNote
for (Iterator<Note> notesIt = notes.iterator(); notesIt.hasNext(); ) {
Note note = notesIt.next();
for (Iterator<Note> otherNotesIt = otherNotes.iterator(); otherNotesIt.hasNext(); ) {
Note otherNote = otherNotesIt.next();
if (note.getLang() == null) {
if (otherNote.getLang() != null) {
continue;
}
}
else {
if (!note.getLang().equals(otherNote.getLang())) {
continue;
}
}
if (note.getValue() == null) {
if (otherNote.getValue() != null) {
continue;
}
}
else {
if (!note.getValue().equals(otherNote.getValue())) {
continue;
}
}
result.add(note);
notesIt.remove();
otherNotesIt.remove();
break;
}
}
// now add the ones left
result.addAll(notes);
result.addAll(otherNotes);
return result;
}
private List<Device> composeDevices(
List<Device> devices,
List<Device> otherDevices) {
ArrayList<Device> result = new ArrayList<Device>();
for (Iterator<Device> it = devices.iterator(); it.hasNext(); ) {
Device device = it.next();
for (Iterator<Device> otherIt = otherDevices.iterator(); otherIt.hasNext(); ) {
Device otherDevice = otherIt.next();
Device compositionDevice = composeDevice(device, otherDevice);
if (compositionDevice != null) {
// the composition has a result
result.add(compositionDevice);
// remove both to not be iterated again
it.remove();
otherIt.remove();
break;
}
}
}
// now add the ones left but replacing the ids
for (Device device : devices) {
device.setId(Utils.getInstance().generateTag());
result.add(device);
}
for (Device device : otherDevices) {
device.setId(Utils.getInstance().generateTag());
result.add(device);
}
return result;
}
private Device composeDevice(Device device,
Device otherDevice) {
/*
* If the <deviceID> of the <device> elements that are published from
* different Presence Sources match
*
* then the PS SHALL
*
* a. Aggregate the non-conflicting elements within one <device>
* element; and
*
* b. Set the <timestamp> of the aggregated <device> element to the most
* recent one among the ones that contribute to the aggregation; and
*
* c. Use the element from the most recent publication for conflicting
* elements.
*
*/
if (device.getDeviceID().equals(otherDevice.getDeviceID())) {
Device result = new Device();
// process timestamp
boolean deviceIsOlder = false;
if (device.getTimestamp() == null) {
result.setTimestamp(otherDevice.getTimestamp());
deviceIsOlder = true;
}
else {
if (otherDevice.getTimestamp() == null) {
result.setTimestamp(device.getTimestamp());
}
else {
if (device.getTimestamp().compare(otherDevice.getTimestamp()) > 0) {
result.setTimestamp(device.getTimestamp());
}
else {
result.setTimestamp(otherDevice.getTimestamp());
deviceIsOlder = true;
}
}
}
// process anys
result.getAny().addAll(compose(device.getAny(), otherDevice.getAny(),true,true,deviceIsOlder));
// process notes
result.getNote().addAll(composeNotesT(device.getNote(), otherDevice.getNote()));
result.setDeviceID(device.getDeviceID());
result.setId(Utils.getInstance().generateTag());
return result;
}
else {
return null;
}
}
private List<Person> composePersons(
List<Person> persons,
List<Person> otherPersons) {
ArrayList<Person> result = new ArrayList<Person>();
for (Iterator<Person> it = persons.iterator(); it.hasNext(); ) {
Person person = it.next();
for (Iterator<Person> otherIt = otherPersons.iterator(); otherIt.hasNext(); ) {
Person otherPerson = otherIt.next();
Person compositionPerson = composePerson(person, otherPerson);
if (compositionPerson != null) {
// the composition has a result
result.add(compositionPerson);
// remove both to not be iterated again
it.remove();
otherIt.remove();
break;
}
}
}
// now add the ones left but replacing the ids
for (Person person : persons) {
person.setId(Utils.getInstance().generateTag());
result.add(person);
}
for (Person person : otherPersons) {
person.setId(Utils.getInstance().generateTag());
result.add(person);
}
return result;
}
private Person composePerson(Person person,
Person otherPerson) {
/*
* If the following conditions all apply:
*
* a. If one <person> element includes a <class> element, as defined in
* section 10.5.1, other <person> elements include an identical <class>
* element; and
*
* b. If there are no conflicting elements (same elements with different
* values or attributes) under the <person> elements. Different
* <timestamp> values are not considered as a conflict.
*
* then the PS SHALL:
*
* a. Aggregate elements within a <person> element that are published
* from different Presence Sources into one <person> element. Identical
* elements with the same value SHALL not be duplicated.
*
* b. Set the <timestamp> of the aggregated <person> element to the most
* recent one among the ones that contribute to the aggregation. In any
* other case, the PS SHALL keep <person> elements from different
* Presence Sources separate.
*/
Person result = new Person();
// process anys
List<Object> anys = compose(person.getAny(), otherPerson.getAny(),false,false,false);
if (anys != null) {
result.getAny().addAll(anys);
}
else {
return null;
}
// process timestamp
if (person.getTimestamp() == null) {
result.setTimestamp(otherPerson.getTimestamp());
}
else {
if (otherPerson.getTimestamp() == null) {
result.setTimestamp(person.getTimestamp());
}
else {
if (person.getTimestamp().compare(otherPerson.getTimestamp()) > 0) {
result.setTimestamp(person.getTimestamp());
}
else {
result.setTimestamp(otherPerson.getTimestamp());
}
}
}
// process notes
result.getNote().addAll(composeNotesT(person.getNote(), otherPerson.getNote()));
result.setId(Utils.getInstance().generateTag());
return result;
}
/**
* same thing as composeNotes, just the object type differs
* @param notes
* @param otherNotes
* @return
*/
private List<NoteT> composeNotesT(List<NoteT> notes,List<NoteT> otherNotes) {
ArrayList<NoteT> result = new ArrayList<NoteT>();
// process all from notes trying to match each with otherNote
for (Iterator<NoteT> notesIt = notes.iterator(); notesIt.hasNext(); ) {
NoteT note = notesIt.next();
for (Iterator<NoteT> otherNotesIt = otherNotes.iterator(); otherNotesIt.hasNext(); ) {
NoteT otherNote = otherNotesIt.next();
if (note.getLang() == null) {
if (otherNote.getLang() != null) {
continue;
}
}
else {
if (!note.getLang().equals(otherNote.getLang())) {
continue;
}
}
if (note.getValue() == null) {
if (otherNote.getValue() != null) {
continue;
}
}
else {
if (!note.getValue().equals(otherNote.getValue())) {
continue;
}
}
result.add(note);
notesIt.remove();
otherNotesIt.remove();
break;
}
}
// now add the ones left
result.addAll(notes);
result.addAll(otherNotes);
return result;
}
}