/** * 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.user.refinement.impl.location; import java.util.Comparator; import java.util.Date; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.societies.api.context.CtxException; import org.societies.api.context.event.CtxChangeEvent; import org.societies.api.context.event.CtxChangeEventListener; 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.CtxAttributeIdentifier; 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.CtxOriginType; import org.societies.api.context.source.CtxSourceNames; import org.societies.api.internal.context.broker.ICtxBroker; import org.societies.api.internal.context.model.CtxAssociationTypes; import org.societies.api.internal.context.model.CtxAttributeTypes; import org.societies.context.api.user.inference.UserCtxInferenceException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * Describe your class here... * * @author <a href="mailto:nicolas.liampotis@cn.ntua.gr">Nicolas Liampotis</a> (ICCS) * @since 0.5 */ @Service public class UserLocationRefiner { /** The logging facility. */ private static final Logger LOG = LoggerFactory.getLogger(UserLocationRefiner.class); @Autowired(required=true) private ICtxBroker internalCtxBroker; private final Set<CtxAttributeIdentifier> continuouslyRefinedAttrIds = new CopyOnWriteArraySet<CtxAttributeIdentifier>(); private final ExecutorService executorService = Executors.newSingleThreadExecutor(); static Comparator<CtxAttribute> LocationSymbolicComparator = new Comparator<CtxAttribute>() { @Override public int compare(CtxAttribute attr1, CtxAttribute attr2) { if (attr1 == null && attr2 == null) return 0; // we want null values first if (attr1 != null && attr2 == null) return +1; if (attr1 == null && attr2 != null) return -1; final long now = new Date().getTime(); final boolean isAttr1Fresh = this.isFresh(attr1, now); final boolean isAttr2Fresh = this.isFresh(attr2, now); if (isAttr1Fresh && isAttr2Fresh) { // both attributes are fresh if (LOG.isDebugEnabled()) LOG.debug("attr1 and attr2 fresh"); if (attr1.getSourceId() == null && attr2.getSourceId() != null) return -1; else if (attr1.getSourceId() != null && attr2.getSourceId() == null) return +1; else if (attr1.getSourceId() == null && attr2.getSourceId() == null) return attr1.getQuality().getLastUpdated().compareTo(attr2.getQuality().getLastUpdated()); else if (attr1.getSourceId().contains(CtxSourceNames.PZ) && attr2.getSourceId().contains(CtxSourceNames.RFID)) return -1; else if ((attr1.getSourceId().contains(CtxSourceNames.PZ) && attr2.getSourceId().contains(CtxSourceNames.PZ)) || (attr1.getSourceId().contains(CtxSourceNames.RFID) && attr2.getSourceId().contains(CtxSourceNames.RFID))) return attr1.getQuality().getLastUpdated().compareTo(attr2.getQuality().getLastUpdated()); else if (attr1.getSourceId().contains(CtxSourceNames.RFID) && attr2.getSourceId().contains(CtxSourceNames.PZ)) return +1; else if (attr2.getSourceId().contains(CtxSourceNames.PZ) || attr2.getSourceId().contains(CtxSourceNames.RFID)) return -1; else if (attr1.getSourceId().contains(CtxSourceNames.PZ) || attr1.getSourceId().contains(CtxSourceNames.RFID)) return +1; else return attr1.getQuality().getLastUpdated().compareTo(attr2.getQuality().getLastUpdated()); } else if (isAttr1Fresh) { // a1 is fresh if (LOG.isDebugEnabled()) LOG.debug("attr1 fresh"); return +1; } else if (isAttr2Fresh) { // a2 is fresh if (LOG.isDebugEnabled()) LOG.debug("attr2 fresh"); return -1; } else { // none of the attributes is fresh if (LOG.isDebugEnabled()) LOG.debug("attr1 and attr2 NOT fresh"); return attr1.getQuality().getLastUpdated().compareTo(attr2.getQuality().getLastUpdated()); } } private boolean isFresh(final CtxAttribute attr, final long now) { final long timeSinceLastUpdate = now - attr.getQuality().getLastUpdated().getTime(); final Double timeBetweenUpdates = (attr.getQuality().getUpdateFrequency() != null) ? (1d / attr.getQuality().getUpdateFrequency()) * 1000d : null; return (timeBetweenUpdates != null) ? timeBetweenUpdates > timeSinceLastUpdate : true; } }; UserLocationRefiner() { if (LOG.isInfoEnabled()) LOG.info(this.getClass() + " instantiated"); } public CtxAttribute refineOnDemandGPSCoords(final CtxAttributeIdentifier attrId) throws UserCtxInferenceException { if (LOG.isDebugEnabled()) LOG.debug("Refining attribute '" + attrId + "' on-demand"); if (!CtxAttributeTypes.LOCATION_COORDINATES.equals(attrId.getType())) throw new UserCtxInferenceException("Could not refine attribute '" + attrId + "': Unsupported attribute type: " + attrId.getType()); // TODO add real inference CtxAttribute refinedAttr ; try { refinedAttr = this.internalCtxBroker.retrieveAttribute(attrId, false).get(); } catch (Exception e) { throw new UserCtxInferenceException("Could not refine attribute '" + attrId + "': " + e.getLocalizedMessage(), e); } return refinedAttr; } public CtxAttribute refineOnDemand(final CtxAttributeIdentifier attrId) throws UserCtxInferenceException { if (LOG.isDebugEnabled()) LOG.debug("Refining attribute '" + attrId + "' on-demand"); if (!CtxAttributeTypes.LOCATION_SYMBOLIC.equals(attrId.getType())) throw new UserCtxInferenceException("Could not refine attribute '" + attrId + "': Unsupported attribute type: " + attrId.getType()); final CtxEntityIdentifier ownerEntId = attrId.getScope(); if (LOG.isDebugEnabled()) LOG.debug("ownerEntId=" + ownerEntId); try { final CtxEntity ownerEnt = (CtxEntity) this.internalCtxBroker.retrieve(ownerEntId).get(); if (ownerEnt == null) throw new UserCtxInferenceException("Could not refine attribute '" + attrId + "': Owner entity '" + ownerEnt + "' does not exist"); if (ownerEnt.getAssociations(CtxAssociationTypes.OWNS_CSS_NODES).isEmpty()) return null; // Cannot refine without OWNS_CSS_NODES association final CtxAssociationIdentifier ownsCssNodesAssocId = ownerEnt.getAssociations(CtxAssociationTypes.OWNS_CSS_NODES).iterator().next(); if (LOG.isDebugEnabled()) LOG.debug("ownsCssNodesAssocId=" + ownsCssNodesAssocId); final CtxAssociation ownsCssNodesAssoc = (CtxAssociation) this.internalCtxBroker.retrieve(ownsCssNodesAssocId).get(); if (ownsCssNodesAssoc == null) throw new UserCtxInferenceException("Could not refine attribute '" + attrId + "': Association '" + ownsCssNodesAssocId + "' does not exist"); if (!ownerEntId.equals(ownsCssNodesAssoc.getParentEntity())) return null; // Cannot refine if the CSS owner entity is not the parent of the OWNS_CSS_NODES association if (ownsCssNodesAssoc.getChildEntities().isEmpty()) return null; // Cannot refine without CSS_NODE entities // TODO select User Interaction Node; pick first for now final CtxEntityIdentifier cssNodeEntId = ownsCssNodesAssoc.getChildEntities().iterator().next(); if (LOG.isDebugEnabled()) LOG.debug("cssNodeEntId=" + cssNodeEntId); final CtxEntity cssNodeEnt = (CtxEntity) this.internalCtxBroker.retrieve(cssNodeEntId).get(); if (cssNodeEnt == null) throw new UserCtxInferenceException("Could not refine attribute '" + attrId + "': Entity '" + cssNodeEntId + "' does not exist"); final Set<CtxAttribute> inputAttrs = cssNodeEnt.getAttributes(attrId.getType()); if (LOG.isDebugEnabled()) LOG.debug("inputAttrs.size()=" + inputAttrs.size()); if (inputAttrs.isEmpty()) return null; // Cannot refine without attributes of the specified type under the CSS_NODE entity final SortedSet<CtxAttribute> sortedInputAttrs = new TreeSet<CtxAttribute>(LocationSymbolicComparator); sortedInputAttrs.addAll(inputAttrs); final CtxAttribute optimalInputAttr = sortedInputAttrs.last(); if (LOG.isDebugEnabled()) LOG.debug("optimalInputAttr=" + optimalInputAttr.getId()); final CtxAttribute refinedAttr = this.internalCtxBroker.retrieveAttribute(attrId, false).get(); refinedAttr.setStringValue(optimalInputAttr.getStringValue()); refinedAttr.setValueType(CtxAttributeValueType.STRING); refinedAttr.setSourceId("UserLocationRefiner"); refinedAttr.getQuality().setOriginType(CtxOriginType.INFERRED); if (optimalInputAttr.getQuality().getUpdateFrequency() != null) refinedAttr.getQuality().setUpdateFrequency(optimalInputAttr.getQuality().getUpdateFrequency()); return refinedAttr; } catch (Exception e) { throw new UserCtxInferenceException("Could not refine attribute '" + attrId + "': " + e.getLocalizedMessage(), e); } } public void refineContinuously(final CtxAttributeIdentifier attrId, final Double updateFrequency) throws UserCtxInferenceException { // TODO handle updateFrequency if (LOG.isDebugEnabled()) LOG.debug("Refining attribute " + attrId + " continuously"); if (!CtxAttributeTypes.LOCATION_SYMBOLIC.equals(attrId.getType())) throw new UserCtxInferenceException("Could not refine attribute '" + attrId + "': Unsupported attribute type: " + attrId.getType()); if (this.continuouslyRefinedAttrIds.contains(attrId)) { if (LOG.isDebugEnabled()) LOG.debug("Attribute " + attrId + " already continuously inferred"); return; } final CtxEntityIdentifier ownerEntId = attrId.getScope(); if (LOG.isDebugEnabled()) LOG.debug("ownerEntId=" + ownerEntId); try { final CtxEntity ownerEnt = (CtxEntity) this.internalCtxBroker.retrieve(ownerEntId).get(); if (ownerEnt == null) throw new UserCtxInferenceException("Could not refine attribute '" + attrId + "': Owner entity '" + ownerEnt + "' does not exist"); if (ownerEnt.getAssociations(CtxAssociationTypes.OWNS_CSS_NODES).isEmpty()) return; // Cannot refine without OWNS_CSS_NODES association final CtxAssociationIdentifier ownsCssNodesAssocId = ownerEnt.getAssociations(CtxAssociationTypes.OWNS_CSS_NODES).iterator().next(); if (LOG.isDebugEnabled()) LOG.debug("ownsCssNodesAssocId=" + ownsCssNodesAssocId); final CtxAssociation ownsCssNodesAssoc = (CtxAssociation) this.internalCtxBroker.retrieve(ownsCssNodesAssocId).get(); if (ownsCssNodesAssoc == null) throw new UserCtxInferenceException("Could not refine attribute '" + attrId + "': Association '" + ownsCssNodesAssocId + "' does not exist"); if (ownsCssNodesAssoc.getChildEntities().isEmpty()) return; // Cannot refine without CSS_NODE entities if (!ownerEntId.equals(ownsCssNodesAssoc.getParentEntity())) return; // Should not refine attributes under the CSS_NODE entity // TODO select User Interaction Node; pick first for now final CtxEntityIdentifier cssNodeEntId = ownsCssNodesAssoc.getChildEntities().iterator().next(); if (LOG.isDebugEnabled()) LOG.debug("cssNodeEntId=" + cssNodeEntId); this.internalCtxBroker.registerForChanges( new LocationSymbolicChangeListener(attrId), cssNodeEntId, attrId.getType()); if (LOG.isDebugEnabled()) LOG.debug("Adding " + attrId + " to set of continuously inferred attributes"); this.continuouslyRefinedAttrIds.add(attrId); } catch (Exception e) { throw new UserCtxInferenceException("Could not continuoysly refine attribute '" + attrId + "': " + e.getLocalizedMessage(), e); } } private class LocationSymbolicChangeListener implements CtxChangeEventListener { private final CtxAttributeIdentifier attrId; private LocationSymbolicChangeListener (final CtxAttributeIdentifier attrId) { this.attrId = attrId; } /* * @see org.societies.api.context.event.CtxChangeEventListener#onCreation(org.societies.api.context.event.CtxChangeEvent) */ @Override public void onCreation(CtxChangeEvent event) { // TODO Auto-generated method stub } /* * @see org.societies.api.context.event.CtxChangeEventListener#onModification(org.societies.api.context.event.CtxChangeEvent) */ @Override public void onModification(CtxChangeEvent event) { // TODO Auto-generated method stub } /* * @see org.societies.api.context.event.CtxChangeEventListener#onRemoval(org.societies.api.context.event.CtxChangeEvent) */ @Override public void onRemoval(CtxChangeEvent event) { // TODO Auto-generated method stub } /* (non-Javadoc) * @see org.societies.api.context.event.CtxChangeEventListener#onUpdate(org.societies.api.context.event.CtxChangeEvent) */ @Override public void onUpdate(CtxChangeEvent event) { if (LOG.isDebugEnabled()) LOG.debug("LocationSymbolicChangeListener received event " + event); executorService.execute(new LocationSymbolicChangeHandler(this.attrId)); } } private class LocationSymbolicChangeHandler implements Runnable { private final CtxAttributeIdentifier attrId; private LocationSymbolicChangeHandler(final CtxAttributeIdentifier attrId) { this.attrId = attrId; } /* * @see java.lang.Runnable#run() */ @Override public void run() { if (LOG.isDebugEnabled()) LOG.debug("Handling LOCATION_SYMBOLIC update to refine attribute " + this.attrId); try { final CtxAttribute refinedAttr = refineOnDemand(this.attrId); if (LOG.isDebugEnabled()) LOG.debug("Refined attribute " + refinedAttr); // TODO send refinedAttr to UserCtxInferenceMgr if (refinedAttr != null) internalCtxBroker.update(refinedAttr); } catch (Exception e) { LOG.error("Could not handle LOCATION_SYMBOLIC update to refine attribute " + this.attrId + ": " + e.getLocalizedMessage(), e); } } } }