/**
* Copyright (c) 2011, SOCIETIES Consortium (WATERFORD INSTITUTE OF TECHNOLOGY (TSSG), HERIOT-WATT UNIVERSITY (HWU), SOLUTA.NET
* (SN), GERMAN AEROSPACE CENTRE (Deutsches Zentrum fuer Luft- und Raumfahrt e.V.) (DLR), Zavod za varnostne tehnologije
* informacijske družbe in elektronsko poslovanje (SETCCE), INSTITUTE OF COMMUNICATION AND COMPUTER SYSTEMS (ICCS), LAKE
* COMMUNICATIONS (LAKE), INTEL PERFORMANCE LEARNING SOLUTIONS LTD (INTEL), PORTUGAL TELECOM INOVAÇÃO, SA (PTIN), IBM Corp.,
* INSTITUT TELECOM (ITSUD), AMITEC DIACHYTI EFYIA PLIROFORIKI KAI EPIKINONIES ETERIA PERIORISMENIS EFTHINIS (AMITEC), TELECOM
* ITALIA S.p.a.(TI), TRIALOG (TRIALOG), Stiftelsen SINTEF (SINTEF), NEC EUROPE LTD (NEC))
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
* conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.societies.context.source.impl;
import java.io.IOException;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.societies.api.comm.xmpp.interfaces.ICommManager;
import org.societies.api.context.CtxException;
import org.societies.api.context.model.CtxAssociation;
import org.societies.api.context.model.CtxAssociationIdentifier;
import org.societies.api.context.model.CtxAttribute;
import org.societies.api.context.model.CtxAttributeValueType;
import org.societies.api.context.model.CtxEntity;
import org.societies.api.context.model.CtxEntityIdentifier;
import org.societies.api.context.model.CtxIdentifier;
import org.societies.api.context.model.CtxModelType;
import org.societies.api.context.model.CtxOriginType;
import org.societies.api.context.model.CtxQuality;
import org.societies.api.context.model.util.SerialisationHelper;
import org.societies.api.context.source.ICtxSourceMgr;
import org.societies.api.identity.INetworkNode;
import org.societies.api.internal.context.broker.*;
import org.societies.api.internal.context.model.CtxAssociationTypes;
import org.societies.api.internal.context.model.CtxAttributeTypes;
import org.societies.api.internal.context.model.CtxEntityTypes;
import org.societies.api.internal.logging.IPerformanceMessage;
import org.societies.api.internal.logging.PerformanceMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
@Service
public class ContextSourceManagement implements ICtxSourceMgr {
private static Logger LOG = LoggerFactory.getLogger(ContextSourceManagement.class);
private static Logger PERF_LOG = LoggerFactory.getLogger("PerformanceMessage"); // to define a dedicated Logger for Performance Testing
/**
* The Context Broker service reference.
*
* @see {@link #setCtxBroker(ICtxBroker)}
*/
private ICtxBroker ctxBroker;
/**
* The Communication Manager service reference.
*
* @see {@link #getCommManager()}
* @see {@link #setCommMgr(ICommManager)}
*/
@Autowired(required = true)
private ICommManager commMgr;
private volatile int counter = 0;
@Autowired(required=true)
public ContextSourceManagement(ICtxBroker ctxBroker) throws Exception {
if (LOG.isInfoEnabled())
LOG.info(this.getClass() + " instantiated");
this.ctxBroker = ctxBroker;
try {
final List<CtxIdentifier> sourceEntIds = this.ctxBroker.lookup(
CtxModelType.ENTITY, CtxEntityTypes.CONTEXT_SOURCE).get();
for (final CtxIdentifier sourceEntId : sourceEntIds) {
if (LOG.isDebugEnabled())
LOG.debug("Removing " + CtxEntityTypes.CONTEXT_SOURCE + " entity " + sourceEntId);
this.ctxBroker.remove(sourceEntId);
}
if (LOG.isInfoEnabled())
LOG.info("Removed " + sourceEntIds.size() + " obsolete context source registration(s)");
} catch (Exception e) {
LOG.error("Could not initialise CSM: " + e.getLocalizedMessage(), e);
throw e;
}
}
/**
* Empty constructor used for junit testing
*/
public ContextSourceManagement() {}
/*
* @see org.societies.api.context.source.ICtxSourceMgr#register(java.lang.String, java.lang.String)
*/
@Override
@Async
public Future<String> register(String name, String contextType) {
return registerFull(null, name, contextType, null);
}
/*
* @see org.societies.api.context.source.ICtxSourceMgr#register(org.societies.api.identity.INetworkNode, java.lang.String, java.lang.String)
*/
@Override
@Async
public Future<String> register(INetworkNode contextOwner, String name,
String contextType) {
return registerFull(contextOwner, name, contextType, null);
}
/**
* full internal register, used by external register methods and by DeviceManager connector
*/
@Async
Future<String> registerFull(INetworkNode cssNodeId, String name,
String contextType, String id) {
long timestamp = System.nanoTime();
if (this.ctxBroker == null) {
LOG.error("Could not register " + contextType
+ ": Context Broker is not available");
return new AsyncResult<String>(null);
}
if (id == null)
id = name + this.counter++;
try {
// TODO use existing registration if available
final List<CtxEntityIdentifier> shadowEntities = this.ctxBroker.lookupEntities(
CtxEntityTypes.CONTEXT_SOURCE, CtxAttributeTypes.ID, id, id).get();
if (shadowEntities.size() > 0) {
LOG.error("Aborting registration of '" + name + "' context source"
+ ": Generated source id '" + id + "' already in use");
return new AsyncResult<String>(null);
}
final CtxEntity shadowEnt = this.ctxBroker.createEntity(CtxEntityTypes.CONTEXT_SOURCE).get();
// 1. Source ID
final CtxAttribute sourceIdAttr = this.ctxBroker.createAttribute(
shadowEnt.getId(), CtxAttributeTypes.ID).get();
this.ctxBroker.updateAttribute(sourceIdAttr.getId(), id);
// 2. context type
final CtxAttribute ctxTypeAttr = this.ctxBroker.createAttribute(
shadowEnt.getId(), CtxAttributeTypes.TYPE).get();
this.ctxBroker.updateAttribute(ctxTypeAttr.getId(), contextType);
final CtxEntity ctxOwnerEntity;
if (cssNodeId != null) {
ctxOwnerEntity = this.ctxBroker.retrieveCssNode(cssNodeId).get();
} else { // (cssNodeId == null)
ctxOwnerEntity = this.ctxBroker.retrieveCssNode(
this.commMgr.getIdManager().getThisNetworkNode()).get();
}
if (ctxOwnerEntity == null) {
LOG.error("Could not complete registration of '"
+ name + "' context source with id '"
+ id + "': CSS Node entity does not exist"); // TODO
return new AsyncResult<String>(null);
}
final CtxAssociation assocToCtxOwnerEntity = this.ctxBroker
.createAssociation(CtxAssociationTypes.PROVIDES_UPDATES_FOR).get();
assocToCtxOwnerEntity.setParentEntity(shadowEnt.getId());
assocToCtxOwnerEntity.addChildEntity(ctxOwnerEntity.getId());
this.ctxBroker.update(assocToCtxOwnerEntity);
// performance logging
IPerformanceMessage m = new PerformanceMessage();
m.setTestContext("CSM_Delay_ComponentInternal");
m.setSourceComponent(this.getClass()+"");
m.setPerformanceType(IPerformanceMessage.Delay);
m.setOperationType("Register");
m.setD82TestTableName("S11");
m.setPerformanceNameValue("Delay="+(System.nanoTime()-timestamp ));
PERF_LOG.trace(m.toString());
} catch (Exception e) {
LOG.error("Could not complete registration of '"
+ name + "' context source with id '"
+ id + "': " + e.getMessage(), e);
return new AsyncResult<String>(null);
}
return new AsyncResult<String>(id);
}
private Future<Boolean> completeSendUpdate(String identifier,
Serializable data, CtxEntity owner, boolean inferred,
double precision, double frequency, boolean USE_QOC) {
long timestamp = System.nanoTime();
if (this.ctxBroker == null) {
LOG.error("Could not handle update from " + identifier
+ ": Context Broker is not available");
return new AsyncResult<Boolean>(false);
}
if (LOG.isDebugEnabled())
LOG.debug("Sending update: id=" + identifier + ", data=" + data
+ ", ownerEntity=" + owner + ", inferred=" + inferred
+ ", precision=" + precision + ", frequency=" + frequency);
List<CtxEntityIdentifier> shadowEntities;
CtxEntityIdentifier shadowEntityID = null;
Set<CtxAttribute> attrs = null;
CtxEntity shadowEntity = null;
try {
String type = "";
CtxAttribute dataAttr;
CtxQuality quality;
shadowEntities = ctxBroker.lookupEntities(CtxEntityTypes.CONTEXT_SOURCE,
CtxAttributeTypes.ID, identifier, identifier).get();
if (shadowEntities.size() > 1) {
if (LOG.isErrorEnabled())
LOG.error("Sensor-ID " + identifier
+ " is not unique. No information stored.");
return new AsyncResult<Boolean>(false);
// throw new
// Exception("Ambiguity: more than one context source with this identifier exists.");
} else if (shadowEntities.isEmpty()) {
if (LOG.isErrorEnabled())
LOG.error("Sensor-ID " + identifier
+ " is not available. No information stored.");
return new AsyncResult<Boolean>(false);
// throw new
// Exception("Sending failure due to missing Registration.");
} else {
shadowEntityID = shadowEntities.get(0);
shadowEntity = (CtxEntity) this.ctxBroker.retrieve(shadowEntityID)
.get();
}
attrs = shadowEntity.getAttributes(CtxAttributeTypes.TYPE);
if (attrs != null && attrs.size() > 0)
type = attrs.iterator().next().getStringValue();
else
type = "data";
if (LOG.isDebugEnabled())
LOG.debug("type is " + type);
/* update Context Information at Context Source Shadow Entity */
attrs = shadowEntity.getAttributes("data");
if (attrs != null && attrs.size() > 0)
dataAttr = attrs.iterator().next();
else {
dataAttr = this.ctxBroker.createAttribute(shadowEntityID,
"data").get();
}
dataAttr.setSourceId(identifier);
// TODO why? dataAttr.setHistoryRecorded(true);
quality = dataAttr.getQuality();
quality.setOriginType(CtxOriginType.SENSED);
if (USE_QOC) {
if (inferred)
quality.setOriginType(CtxOriginType.INFERRED);
quality.setPrecision(precision);
quality.setUpdateFrequency(frequency);
}
this.updateData(data, dataAttr);
/* update Context Information with Information Owner Entity */
if (LOG.isDebugEnabled())
LOG.debug("Acquiring context owner entity for shadow entity " + shadowEntity.getId());
if (owner == null) {
try {
// Check if the shadow entity has an association to an
// ctxEntity
final Set<CtxAssociationIdentifier> assocIdentifiers =
shadowEntity.getAssociations(CtxAssociationTypes.PROVIDES_UPDATES_FOR);
CtxAssociation providesUpdatesForAssoc;
CtxEntity childEnt;
if (assocIdentifiers.size() != 0) {
for (final CtxAssociationIdentifier ctxId : assocIdentifiers) {
providesUpdatesForAssoc = (CtxAssociation) this.ctxBroker.retrieve(ctxId)
.get();
if (providesUpdatesForAssoc.parentEntity == null
|| !shadowEntity.getId().equals(providesUpdatesForAssoc.getParentEntity())
|| providesUpdatesForAssoc.getChildEntities().isEmpty()) {
if (LOG.isDebugEnabled())
LOG.debug("Ignoring association " + providesUpdatesForAssoc.getId());
continue;
}
childEnt = (CtxEntity) this.ctxBroker.retrieve(
providesUpdatesForAssoc.childEntities.iterator().next()).get();
if (childEnt != null) {
owner = childEnt;
break;
} else {
LOG.error("child is null!!!");
}
}
}
if (owner == null)
owner = this.ctxBroker.retrieveCssNode(
this.commMgr.getIdManager().getThisNetworkNode()).get();
// TODO null check again!!
} catch (Exception e) {
LOG.error("Could not handle update from " + identifier
+ ": Could not retrieve context owner entity: "
+ e.getLocalizedMessage(), e);
return new AsyncResult<Boolean>(false);
}
}
if (LOG.isDebugEnabled())
LOG.debug("Context owner entity for shadow entity " + shadowEntity.getId() + " is " + owner.getId());
attrs = owner.getAttributes(type);
if (attrs.size() > 0) {
for (final CtxAttribute foundAttr : attrs) {
if (foundAttr.getSourceId() != null && identifier.equals(foundAttr.getSourceId())) {
dataAttr = foundAttr;
break;
}
}
} else {
dataAttr = this.ctxBroker.createAttribute(owner.getId(), type).get();
dataAttr.setSourceId(identifier);
}
if (LOG.isDebugEnabled())
LOG.debug("dataAttr.getId()=" + dataAttr.getId());
// Update QoC information.
quality = dataAttr.getQuality();
quality.setOriginType(CtxOriginType.SENSED);
if (USE_QOC) {
if (inferred)
quality.setOriginType(CtxOriginType.INFERRED);
quality.setPrecision(precision);
quality.setUpdateFrequency(frequency);
}
// Set history recorded flag.
// TODO why? dataAttr.setHistoryRecorded(true);
// Update attribute.
this.updateData(data, dataAttr);
// performance logging
if (PERF_LOG.isTraceEnabled()) {
IPerformanceMessage m = new PerformanceMessage();
m.setTestContext("CSM_Delay_ComponentInternal");
m.setSourceComponent(this.getClass()+"");
m.setPerformanceType(IPerformanceMessage.Delay);
m.setOperationType("SendUpdate");
m.setD82TestTableName("S11");
m.setPerformanceNameValue("Delay="+(System.nanoTime()-timestamp ));
PERF_LOG.trace(m.toString());
}
} catch (Exception e) {
LOG.error("Could not handle update from " + identifier + ": "
+ e.getLocalizedMessage(), e);
return new AsyncResult<Boolean>(false);
}
return new AsyncResult<Boolean>(true);
}
/*
* @see org.societies.api.context.source.ICtxSourceMgr#sendUpdate(java.lang.String, java.io.Serializable, org.societies.api.context.model.CtxEntity)
*/
@Override
@Async
public Future<Boolean> sendUpdate(String identifier, Serializable data,
CtxEntity owner) {
return completeSendUpdate(identifier, data, owner, false, 0, 0, false);
}
@Override
@Async
public Future<Boolean> sendUpdate(String identifier, Serializable data,
CtxEntity owner, boolean inferred, double precision,
double frequency) {
return completeSendUpdate(identifier, data, owner, inferred, precision,
frequency, true);
}
@Override
@Async
public Future<Boolean> sendUpdate(String identifier, Serializable data) {
return completeSendUpdate(identifier, data, null, false, 0, 0, false);
}
private void updateData(Serializable value, CtxAttribute attr)
throws Exception {
if (value instanceof String) {
attr.setStringValue((String) value);
attr.setValueType(CtxAttributeValueType.STRING);
} else if (value instanceof Integer) {
attr.setIntegerValue((Integer) value);
attr.setValueType(CtxAttributeValueType.INTEGER);
} else if (value instanceof Double) {
attr.setDoubleValue((Double) value);
attr.setValueType(CtxAttributeValueType.DOUBLE);
} else if (value instanceof Serializable) {
final byte[] blobBytes = SerialisationHelper.serialise(value);
attr.setBinaryValue(blobBytes);
attr.setValueType(CtxAttributeValueType.BINARY);
} else { // if value == null
attr.setStringValue(null);
attr.setValueType(CtxAttributeValueType.EMPTY);
}
try {
attr = (CtxAttribute) ctxBroker.update(attr).get();
} catch (CtxException cde) {
// If the value is a String attempt to store it as a blob. As the
// String might just be too long.
if (value instanceof String) {
if (LOG.isDebugEnabled())
LOG.debug("Attempting to store String value as a blob");
byte[] blobBytes = null;
try {
blobBytes = SerialisationHelper.serialise(value);
} catch (IOException e) {
LOG.error(e.getMessage());
}
attr.setBinaryValue(blobBytes);
attr.setValueType(CtxAttributeValueType.BINARY);
ctxBroker.update(attr);
} else {
throw cde;
}
} catch (Exception e) {
throw e;
}
}
@Override
@Async
public Future<Boolean> unregister(String identifier) {
if (ctxBroker == null) {
LOG.error("Could not unregister " + identifier
+ ": Context Broker cannot be found");
return new AsyncResult<Boolean>(false);
}
Future<List<CtxEntityIdentifier>> shadowEntitiesFuture;
List<CtxEntityIdentifier> shadowEntities;
CtxIdentifier shadowEntity = null;
try {
shadowEntitiesFuture = ctxBroker.lookupEntities(CtxEntityTypes.CONTEXT_SOURCE,
CtxAttributeTypes.ID, identifier, identifier);
shadowEntities = shadowEntitiesFuture.get();
if (shadowEntities.size() > 1) {
LOG.debug("Sensor-ID " + identifier
+ " is not unique. Sensor could not be unregistered");
return new AsyncResult<Boolean>(false);
// throw new
// Exception("Unregistering failure due to ambiguity.");
} else if (shadowEntities.isEmpty()) {
LOG.debug("Sensor-ID " + identifier
+ " is not available. Sensor could not be unregistered");
return new AsyncResult<Boolean>(false);
// throw new
// Exception("Unregistering failure due to missing Registration.");
} else
shadowEntity = shadowEntities.get(0);
ctxBroker.remove(shadowEntity);
} catch (CtxException e) {
// e.printStackTrace();
LOG.error(e.getMessage());
return new AsyncResult<Boolean>(false);
} catch (InterruptedException e) {
LOG.error(e.getMessage());
return new AsyncResult<Boolean>(false);
} catch (ExecutionException e) {
LOG.error(e.getMessage());
return new AsyncResult<Boolean>(false);
}
return new AsyncResult<Boolean>(true);
}
public ICommManager getCommManager() {
return commMgr;
}
public void setCommManager(ICommManager commManager) {
this.commMgr = commManager;
}
/**
* Sets the Context Broker service reference
*
* @param ctxBroker
* the ctxBroker to set
*/
public void setCtxBroker(ICtxBroker ctxBroker) {
this.ctxBroker = ctxBroker;
}
}