/*
* Dog - Core
*
* Copyright (c) 2009-2014 Dario Bonino, Luigi De Russis and Emiliano Castellina
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package it.polito.elite.dog.core.housemodel.semantic;
import it.polito.elite.dog.core.housemodel.api.HouseModel;
import it.polito.elite.dog.core.housemodel.semantic.api.OntologyModel;
import it.polito.elite.dog.core.housemodel.semantic.loader.ThreadedDeviceAdder;
import it.polito.elite.dog.core.housemodel.semantic.loader.ThreadedDeviceRemover;
import it.polito.elite.dog.core.housemodel.semantic.loader.ThreadedModelLoader;
import it.polito.elite.dog.core.housemodel.semantic.loader.ThreadedModelLoader.LoadingModes;
import it.polito.elite.dog.core.housemodel.semantic.loader.ThreadedModelLoader.ModelTypes;
import it.polito.elite.dog.core.housemodel.semantic.query.SPARQLQueryWrapper;
import it.polito.elite.dog.core.housemodel.semantic.util.DogOnt2XMLDog;
import it.polito.elite.dog.core.library.jaxb.Configcommand;
import it.polito.elite.dog.core.library.jaxb.Confignotification;
import it.polito.elite.dog.core.library.jaxb.Configparam;
import it.polito.elite.dog.core.library.jaxb.Configstate;
import it.polito.elite.dog.core.library.jaxb.ControlFunctionality;
import it.polito.elite.dog.core.library.jaxb.Controllables;
import it.polito.elite.dog.core.library.jaxb.Device;
import it.polito.elite.dog.core.library.jaxb.DogHomeConfiguration;
import it.polito.elite.dog.core.library.jaxb.NotificationFunctionality;
import it.polito.elite.dog.core.library.model.DeviceCostants;
import it.polito.elite.dog.core.library.model.DeviceDescriptor;
import it.polito.elite.dog.core.library.semantic.util.OntologyDescriptorSet;
import it.polito.elite.dog.core.library.util.LogHelper;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.log.LogService;
import com.hp.hpl.jena.ontology.OntModel;
/**
* <p>
* The Semantic House Model bundle manages a DogOnt-based house model inside the
* Dog gateway. <br/>
* It offers average-level semantic capabilities for querying both the DogOnt
* main schema and the house-specific instance run by Dog.
* </p>
*
* <p>
* It is designed to support more advanced query functionalities such as query
* by location, by functionality, by energy profile, etc.
*
* These advanced functionalities are implemented as further extensions of the
* basic HouseModel interface...
* </p>
*
* @author <a href="mailto:dario.bonino@polito.it">Dario Bonino</a>
* @author <a href="mailto:luigi.derussis@polito.it">Luigi De Russis</a>
* @see <a href="http://elite.polito.it">http://elite.polito.it</a>
*
*/
public class SemanticHouseModel implements HouseModel, OntologyModel, ManagedService
{
// the private reference to the service registration object
private ServiceRegistration<?> srHouseModel;
private ServiceRegistration<?> srOntModel;
// the internal reference to the context
private BundleContext context;
// the DogOnt instance SVG footprint
private String homeSVGFootprint;
// the current Jena model used by the bundle
private OntologyDescriptorSet ontoDescSet;
private OntModel reasonedOntModel;
private OntModel plainOntModel;
// the map of currently added models
private HashMap<String, OntModel> modelsInUse;
private HashMap<String, Map<String, String>> namespacesInUse;
// the internal namespace map
private Map<String, String> namespaces;
// the entry point namespace for the current base model
private String entryPoint;
// the query wrapper used to wrap SPARQL queries on to method calls
private SPARQLQueryWrapper qWrapper;
// the logger
private LogHelper logger;
// the XML configuration (for external communication and for describing a
// device)
private DogHomeConfiguration xmlConfiguration;
// the JAXB representation of the devices without network-related info
private List<Controllables> simpleDevicesConfiguration;
/**
* Default (empty) constructor
*/
public SemanticHouseModel()
{
}
/**
* Activate this component
*
* @param bundleContext
* the bundle context
*/
public void activate(BundleContext bundleContext)
{
// init
this.context = bundleContext;
this.logger = new LogHelper(bundleContext);
// prepare the maps for handling modular ontology models
this.modelsInUse = new HashMap<String, OntModel>();
this.namespacesInUse = new HashMap<String, Map<String, String>>();
this.homeSVGFootprint = "no map loaded";
}
/**
* Deactivate this component
*/
public void deactivate()
{
// unregister services
this.unRegister();
// set everything to null
this.context = null;
this.logger = null;
this.modelsInUse = null;
this.namespacesInUse = null;
this.homeSVGFootprint = null;
this.srHouseModel = null;
this.srOntModel = null;
}
/**
* Unregister its services from OSGi framework
*/
public void unRegister()
{
if (this.srHouseModel != null)
{
this.srHouseModel.unregister();
}
if (this.srOntModel != null)
{
this.srOntModel.unregister();
}
}
/**
* Listen for the configuration and start the ontology loading...
*/
@Override
public void updated(Dictionary<String, ?> properties)
{
if (properties != null)
{
String ontologyFileName = (String) properties.get(DeviceCostants.ONTOLOGY);
String svgFileName = (String) properties.get(DeviceCostants.SVGPLAN);
if (ontologyFileName != null && !ontologyFileName.isEmpty())
{
// log the update data received
this.logger.log(LogService.LOG_INFO, "Received ontology configuration...");
// get the XMLString representing the model set to load
String ontoDescSetAsString = this.filetoString(ontologyFileName);
// parse the set
this.ontoDescSet = OntologyDescriptorSet.parse(ontoDescSetAsString);
if ((ontoDescSet != null) && (!ontoDescSet.isEmpty()))
{
// create a new loader Thread pointing at the bundle context
ThreadedModelLoader loader = new ThreadedModelLoader(this);
loader.setModelToLoad(ontoDescSet, LoadingModes.LOAD_REPLACE, ModelTypes.REASONED_WITH_PELLET);
// start loading data...
loader.start();
}
}
if (svgFileName != null && !svgFileName.isEmpty())
{
// load the SVG plan
this.homeSVGFootprint = this.filetoString(svgFileName);
}
}
}
/**
* Register the services provided by this bundle
*/
private void registerServices()
{
if (this.srHouseModel == null)
{
// register the house model service
this.srHouseModel = this.context.registerService(HouseModel.class.getName(), this, null);
}
if (this.srOntModel == null)
{
// register the ontology model service
this.srOntModel = this.context.registerService(OntologyModel.class.getName(), this, null);
}
}
/**
* @return the SVG footprint
*/
public String getSVGPlan()
{
return this.homeSVGFootprint;
}
/**
* @return the logger
*/
public LogHelper getLogger()
{
return logger;
}
/**
* @return the namespaces
*/
public synchronized Map<String, String> getNamespaces()
{
return namespaces;
}
/**
* @return the qWrapper
*/
public synchronized SPARQLQueryWrapper getQWrapper()
{
return qWrapper;
}
/**
* @return the entryPoint
*/
public synchronized String getEntryPoint()
{
return entryPoint;
}
/**
* When called, stores the given Jena OntModel in the instance variable
* named dogontModel and if the driver is still not registered with the
* framework registers the driver as available...
*
* @param model
* the model on which the driver will work
*/
public synchronized void setModel(OntModel plainModel, OntModel reasonedModel, Map<String, String> namespace,
String entrypoint)
{
this.reasonedOntModel = reasonedModel;
this.plainOntModel = plainModel;
this.namespaces = namespace;
this.entryPoint = entrypoint;
// create the query wrapper object
this.qWrapper = new SPARQLQueryWrapper(this.getNamespaces(), this.reasonedOntModel);
this.logger.log(LogService.LOG_INFO, "Loaded the dogont ontology");
// extract the xml-representation needed to answer queries from external
// applications
Runnable XMLConfigWorker = new Runnable() {
@Override
public void run()
{
try
{
logger.log(LogService.LOG_DEBUG, "Computing JAXB configuration...");
// create the XML translator
DogOnt2XMLDog toXMLTranslator = new DogOnt2XMLDog(reasonedOntModel, namespaces, entryPoint);
// get the JAXB configuration
xmlConfiguration = toXMLTranslator.getJAXBXMLDog();
// create the JAXB object representing the device list without their
// network-related properties
createSimpleDevicesRepresentation();
// finish!
logger.log(LogService.LOG_DEBUG, "Generated JAXB configuration.");
}
catch (Exception e)
{
logger.log(LogService.LOG_ERROR, "Error while translating the ontology to JAXBXML ", e);
}
// register the services provided by the bundle
registerServices();
}
};
// start the thread worker
Thread threadXMLConfigWorker = new Thread(XMLConfigWorker);
threadXMLConfigWorker.start();
}
/**
*
* @param model
* @param namespaces
*/
public synchronized void addModel(OntModel model, Map<String, String> namespaces, String entryPoint)
{
// really experimental implementation
// TODO: check if this way of working is correct and efficient... (may
// be not)
System.err.println("Entry Point " + entryPoint);
// ok first let's store a reference to the model in the modelsInUse
this.modelsInUse.put(entryPoint, model);
// second, add the model as a sub model of the current one (do not know
// if it works)
this.plainOntModel.addSubModel(model);
this.reasonedOntModel.addSubModel(model); // Adding model to the reason
// model
// third, add the new namespaces and keep track of them...
this.namespacesInUse.put(entryPoint, namespaces);
this.getNamespaces().putAll(namespaces);
}
/**
* Create the devices representation suitable for external usage (e.g., the
* REST API), without network related informations.
*/
private synchronized void createSimpleDevicesRepresentation()
{
// get the full device representation
this.simpleDevicesConfiguration = this.xmlConfiguration.clone().getControllables();
for (Device dev : this.simpleDevicesConfiguration.get(0).getDevice())
{
this.cleanJaxbDevice(dev);
}
}
/**
* Prepare the JAXB Device to contain the proper information for external
* applications. It removes all the network-related properties and hides
* some redundant arrays for the JSON serialization.
*
* @param device
* the "full" JAXB Device to clean
*/
private synchronized void cleanJaxbDevice(Device device)
{
// store the parameters to be removed from the current device
Vector<Configparam> paramsToRemove = new Vector<Configparam>();
// remove all the "first-level" params, since they are network-related
device.getParam().clear();
// clean the description field
String description = device.getDescription().trim();
description = description.replaceAll("\t", "");
description = description.replaceAll("\n", " ");
device.setDescription(description);
// get all the control functionalites...
List<ControlFunctionality> controlFunctionalities = device.getControlFunctionality();
for (ControlFunctionality controlFunctionality : controlFunctionalities)
{
// get all the commands
for (Configcommand command : controlFunctionality.getCommands().getCommand())
{
for (Configparam param : command.getParam())
{
// get all the command parameters to remove
// (network-related), i.e., preserve only the
// "realCommandName" prop
if (!param.getName().equalsIgnoreCase("realCommandName"))
{
paramsToRemove.add(param);
}
}
// effectively remove the parameters
for (Configparam param : paramsToRemove)
{
command.getParam().remove(param);
}
paramsToRemove.clear();
}
// improve non-XML rendering by creating a redundant array
controlFunctionality.setCommandList(controlFunctionality.getCommands().getCommand());
}
// get all the notification functionalities...
List<NotificationFunctionality> notificationsFunctionalities = device.getNotificationFunctionality();
for (NotificationFunctionality notificationFunctionality : notificationsFunctionalities)
{
// get all the notifications...
for (Confignotification notification : notificationFunctionality.getNotifications().getNotification())
{
for (Configparam param : notification.getParam())
{
// get all the notification parameters to remove
// (network-related), i.e., preserve only the
// "notificationName" and "notificationParamName" props
if ((!param.getName().equalsIgnoreCase("notificationName"))
&& (!param.getName().equalsIgnoreCase("notificationParamName")))
{
paramsToRemove.add(param);
}
}
// effectively remove the parameters
for (Configparam param : paramsToRemove)
{
notification.getParam().remove(param);
}
paramsToRemove.clear();
}
// improve non-XML rendering by creating a redundant array
notificationFunctionality.setNotificationList(notificationFunctionality.getNotifications()
.getNotification());
}
// improve non-XML rendering by creating a redundant array for states
for (Configstate status : device.getState())
status.setStatevalueList(status.getStatevalues().getStatevalue());
}
/*********************************************************************************
*
* HouseModel service - implemented methods
*
********************************************************************************/
@Override
public Vector<DeviceDescriptor> getConfiguration()
{
return this.getDeviceConfig(null);
}
/**
* <p>
* Gets the device configurations in the property format defined by dog and
* obtained through the {@link DogDeviceDescription} object (by calling the
* {@link asDogDeviceConfigurationP()) method. Conditions might be given
* restricting the set of devices for which configurations must be provided
*
*
* back. They are given as an {@link Hashtable} of maximum 2 {@link HashSet}
* <{@link String}>. This sets are respectively referenced by the keys:
* {@link DogDeviceConstants.DEVICEURI} and
* {@link DogDeviceConstants.DEVICE_CATEGORY}.
* </p>
* <p>
* The first set includes a detailed list of devices (URIs) that must be
* included in the extracted configurations, the second, instead, specifies
* a list of device categories (descending from dogOnt:Controllable) whose
* instances must be included in the extracted configurations. If both sets
* are present at the same time, the set union between the two conditions is
* performed returning the configurations of all devices listed in the first
* set and of all the instances of the categories reported in the second
* set, avoiding duplication.
*
* @param condition
* The conditions
* @return The device configurations in the DOG property format
*/
private Vector<DeviceDescriptor> getDeviceConfig(Hashtable<String, HashSet<String>> condition)
{
// prepare the device description object
Vector<DeviceDescriptor> configs = new Vector<DeviceDescriptor>();
// the device list
Set<String> devList = new HashSet<String>();
// check the condition
if ((condition != null) && (!condition.isEmpty()))
{
// check the directly specified URIs
Set<String> conditions = condition.get(DeviceCostants.DEVICEURI);
if (conditions != null)
{
for (String cCond : conditions)
{
cCond = this.qWrapper.checkAndRepairDogOntNameSpace(cCond);
devList.add(cCond);
}
}
// check the device categories
conditions = condition.get(DeviceCostants.DEVICE_CATEGORY);
// if not null, must extract the set of classes descending from the
// given device categories
if (conditions != null)
{
Set<String> devices = this.qWrapper.getCategoryFilteredControllableInstances(conditions);
// load all to the device list and convert it in short form
for (String devURI : devices)
{
devList.add(this.qWrapper.toShortForm(devURI));
}
}
}
else
{
// get all controllables...
Set<String> devices = this.qWrapper.getAllControllableInstances();
// convert it in short form
for (String devURI : devices)
{
devList.add(this.qWrapper.toShortForm(devURI));
}
}
if (this.xmlConfiguration != null)
{
for (Device dev : this.xmlConfiguration.getControllables().get(0).getDevice())
{
if (devList.contains(dev.getId()))
{
configs.add(new DeviceDescriptor(dev));
}
}
}
return configs;
}
@Override
public void updateConfiguration(Vector<DeviceDescriptor> updatedDescriptors)
{
for (DeviceDescriptor descriptor : updatedDescriptors)
{
this.updateConfiguration(descriptor);
}
}
@Override
public void updateConfiguration(DeviceDescriptor updatedDescriptor)
{
this.removeDeviceImpl(updatedDescriptor.getDeviceURI());
this.addNewDeviceImpl(updatedDescriptor);
}
@Override
public void addToConfiguration(Vector<DeviceDescriptor> newDescriptors)
{
for (DeviceDescriptor dd : newDescriptors)
{
this.addToConfiguration(dd);
}
}
@Override
public void addToConfiguration(DeviceDescriptor newDescriptor)
{
this.addNewDeviceImpl(newDescriptor);
}
@Override
public void removeFromConfiguration(Set<String> deviceURIs)
{
for (String device : deviceURIs)
{
this.removeFromConfiguration(device);
}
}
@Override
public void removeFromConfiguration(String deviceURI)
{
this.removeDeviceImpl(deviceURI);
}
private void addNewDeviceImpl(DeviceDescriptor deviceDescriptor)
{
// log the addition request
this.logger.log(LogService.LOG_INFO, "Received request to add: " + deviceDescriptor.getDeviceURI());
// create and launch the device addition thread
ThreadedDeviceAdder devAdder = new ThreadedDeviceAdder(deviceDescriptor, this.reasonedOntModel,
this.plainOntModel, this);
devAdder.run();
}
private void removeDeviceImpl(String deviceURI)
{
this.logger.log(LogService.LOG_INFO, "Received request to remove: " + deviceURI);
// create and launch the device removal thread
ThreadedDeviceRemover devRemover = new ThreadedDeviceRemover(deviceURI, this.reasonedOntModel,
this.plainOntModel, this);
devRemover.start();
}
@Override
public DogHomeConfiguration getJAXBConfiguration()
{
// return the complete JAXB configuration (i.e., rooms, devices,
// low-level properties, etc.)
return this.xmlConfiguration;
}
@Override
public List<Controllables> getJAXBDevices()
{
List<Controllables> devices = new ArrayList<Controllables>();
if ((this.xmlConfiguration != null) && (!this.xmlConfiguration.getControllables().isEmpty()))
{
devices = this.xmlConfiguration.clone().getControllables();
}
// return all the devices with their properties, in their JAXB
// representation
return devices;
}
@Override
public List<Controllables> getDevices()
{
return this.xmlConfiguration.getControllables();
}
@Override
public List<Controllables> getSimpleDevices()
{
return this.simpleDevicesConfiguration;
}
/*********************************************************************************
*
* OntologyModel service - implemented methods
*
********************************************************************************/
@Override
public OntModel getModel()
{
return this.reasonedOntModel;
}
@Override
public void loadAndMerge(OntologyDescriptorSet setToLoad)
{
this.loadAndMergeImpl(setToLoad);
}
@Override
public void remove(Set<String> modelsToRemove)
{
this.removeImpl(modelsToRemove);
}
private void loadAndMergeImpl(OntologyDescriptorSet setToLoad)
{
// call a threaded model loader in merge mode
ThreadedModelLoader loader = new ThreadedModelLoader(this);
loader.setModelToLoad(setToLoad, LoadingModes.LOAD_MERGE, ModelTypes.PLAIN_IN_MEMORY); // to
// check
// if
// plain
// is
// sufficient
// start loading data...
loader.start();
}
// TODO: if too intensive it must be moved to a separated worker thread...
private synchronized void removeImpl(Set<String> modelsToRemove)
{
OntModel cOntModel;
Map<String, String> cNamespaces;
// first remove the model if in Use
for (String cModel : modelsToRemove)
{
System.err.println(cModel);
// get the corresponding OntModel
cOntModel = this.modelsInUse.get(cModel);
// if it exists, remove it from the currently used model
if (cOntModel != null)
{
this.reasonedOntModel.remove(cOntModel);
// this.dogontModel.removeSubModel(cOntModel);
this.reasonedOntModel.write(System.out);
}
// remove the model
this.modelsInUse.remove(cModel);
// get the namespaces associated to this model and
cNamespaces = this.namespacesInUse.get(cModel);
// remove them from the list of used namespaces
if (cNamespaces != null)
{
for (String namespace : cNamespaces.keySet())
{
this.getNamespaces().remove(namespace);
}
}
// remove the namespace entry
this.namespacesInUse.remove(cModel);
}
// restore the list of still valid namespaces
for (Map<String, String> oNamespaces : this.namespacesInUse.values())
{
this.getNamespaces().putAll(oNamespaces);
}
this.logger.log(LogService.LOG_INFO, "Loading the Reset Model");
}
/*********************************************************************************
*
* Helper methods
*
********************************************************************************/
/**
* Load a file in memory.
*
* @param propFile
*/
private String filetoString(String fileName)
{
FileInputStream inputStream;
StringBuffer buffer = null;
try
{
inputStream = new FileInputStream(System.getProperty("configFolder") + "/" + fileName);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line = null;
buffer = new StringBuffer();
while ((line = reader.readLine()) != null)
{
buffer.append(line);
}
reader.close();
}
catch (IOException e)
{
e.printStackTrace();
}
return buffer.toString();
}
}