/* AWE - Amanzi Wireless Explorer
* http://awe.amanzi.org
* (C) 2008-2009, AmanziTel AB
*
* This library is provided under the terms of the Eclipse Public License
* as described at http://www.eclipse.org/legal/epl-v10.html. Any use,
* reproduction or distribution of the library constitutes recipient's
* acceptance of this agreement.
*
* This library is distributed WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
package org.amanzi.neo.models.impl.network;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.amanzi.awe.filters.IFilter;
import org.amanzi.neo.dto.IDataElement;
import org.amanzi.neo.impl.dto.DataElement;
import org.amanzi.neo.impl.dto.SectorElement;
import org.amanzi.neo.impl.dto.SiteElement;
import org.amanzi.neo.impl.util.IDataElementIterator;
import org.amanzi.neo.models.exceptions.ModelException;
import org.amanzi.neo.models.exceptions.ParameterInconsistencyException;
import org.amanzi.neo.models.impl.internal.AbstractDatasetModel;
import org.amanzi.neo.models.network.INetworkModel;
import org.amanzi.neo.models.network.NetworkElementType;
import org.amanzi.neo.models.render.IGISModel.ILocationElement;
import org.amanzi.neo.nodeproperties.IGeneralNodeProperties;
import org.amanzi.neo.nodeproperties.IGeoNodeProperties;
import org.amanzi.neo.nodeproperties.INetworkNodeProperties;
import org.amanzi.neo.nodetypes.INodeType;
import org.amanzi.neo.nodetypes.NodeTypeManager;
import org.amanzi.neo.nodetypes.NodeTypeNotExistsException;
import org.amanzi.neo.services.INodeService;
import org.amanzi.neo.services.exceptions.ServiceException;
import org.amanzi.neo.services.impl.NodeService.NodeServiceRelationshipType;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.map.LRUMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.RelationshipType;
import com.vividsolutions.jts.geom.Envelope;
/**
* TODO Purpose of
* <p>
* </p>
*
* @author Nikolay Lagutko (nikolay.lagutko@amanzitel.com)
* @since 1.0.0
*/
public class NetworkModel extends AbstractDatasetModel implements INetworkModel {
// TODO: LN: 12.09.2012, duplicates AbstractMeasurementModel.ElementLocationIterator
private final class ElementLocationIterator implements IDataElementIterator<ILocationElement> {
private final Iterator<IDataElement> dataElements;
private ILocationElement nextElement;
private boolean moveToNext;
private final Set<ILocationElement> locationElements = new HashSet<ILocationElement>();
public ElementLocationIterator(final Iterable<IDataElement> dataElements) {
this.dataElements = dataElements.iterator();
moveToNext = true;
}
@Override
public boolean hasNext() {
if (moveToNext) {
nextElement = moveToNext();
}
return nextElement != null;
}
private ILocationElement moveToNext() {
try {
ILocationElement element = null;
while (dataElements.hasNext() && element == null) {
final IDataElement dataElement = dataElements.next();
if (dataElement.getNodeType().equals(NetworkElementType.SITE)) {
element = getLocationElement(((DataElement)dataElement).getNode());
} else if (dataElement.getNodeType() instanceof NetworkElementType) {
if (NetworkElementType.compare(NetworkElementType.SITE, (NetworkElementType)dataElement.getNodeType()) < 0) {
IDataElement tempElement = dataElement;
while (!tempElement.getNodeType().equals(NetworkElementType.SITE)) {
tempElement = NetworkModel.this.getParentElement(tempElement);
}
element = getLocationElement(((DataElement)tempElement).getNode());
}
}
}
if (element != null) {
locationElements.add(element);
}
return element;
} catch (final ModelException e) {
LOGGER.error(e);
return null;
} finally {
moveToNext = false;
}
}
@Override
public ILocationElement next() {
if (moveToNext) {
nextElement = moveToNext();
}
moveToNext = true;
return nextElement;
}
@Override
public void remove() {
dataElements.remove();
}
@Override
public Iterable<ILocationElement> toIterable() {
return new Iterable<ILocationElement>() {
@Override
public Iterator<ILocationElement> iterator() {
return ElementLocationIterator.this;
}
};
}
}
public enum NetworkRelationshipTypes implements RelationshipType {
SYNONYMS;
}
private static final Logger LOGGER = Logger.getLogger(NetworkModel.class);
private static final String SYNONYMS_KEY_FORMAT = "%s.%s";
private final INetworkNodeProperties networkNodeProperties;
private final LRUMap elementLocationsIteratorCache = new LRUMap(5);
private List<INodeType> structure = new ArrayList<INodeType>() {
/** long serialVersionUID field */
private static final long serialVersionUID = 7149098047373556881L;
{
add(NetworkElementType.NETWORK);
}
};
private Map<String, String> synonyms;
private Node synonymNode;
/**
* @param nodeService
* @param generalNodeProperties
*/
public NetworkModel(final INodeService nodeService, final IGeneralNodeProperties generalNodeProperties,
final IGeoNodeProperties geoNodeProperties, final INetworkNodeProperties networkNodeProperties) {
super(nodeService, generalNodeProperties, geoNodeProperties);
this.networkNodeProperties = networkNodeProperties;
}
protected DataElement createDefaultElement(final INetworkElementType elementType, final IDataElement parent, final String name,
final Map<String, Object> properties) throws ModelException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getStartLogStatement("createDefaultElement", elementType, parent, name, properties));
}
DataElement result = null;
try {
final Node parentNode = ((DataElement)parent).getNode();
final Node node = getNodeService().createNode(parentNode, elementType, NodeServiceRelationshipType.CHILD, name,
properties);
updateNetworkStructure(parent, elementType);
getIndexModel().index(elementType, node, getGeneralNodeProperties().getNodeNameProperty(), name);
getPropertyStatisticsModel().indexElement(elementType, removeIgnoredProperties(properties));
result = new DataElement(node);
result.setName(name);
result.setNodeType(elementType);
} catch (final ServiceException e) {
processException("An error occured on creating new Network Element", e);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getFinishLogStatement("createDefaultElement"));
}
return result;
}
@Override
public IDataElement createElement(final INetworkElementType elementType, final IDataElement parent, final String name,
final Map<String, Object> properties) throws ModelException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getStartLogStatement("createElement", elementType, parent, name, properties));
}
// validate input
if (elementType == null) {
throw new ParameterInconsistencyException("elementType");
}
if (parent == null) {
throw new ParameterInconsistencyException("parent");
}
if (StringUtils.isEmpty(name)) {
throw new ParameterInconsistencyException("name", name);
}
if (properties == null) {
throw new ParameterInconsistencyException("properties", null);
}
IDataElement result = null;
if (elementType.equals(NetworkElementType.SECTOR)) {
final Integer ci = (Integer)properties.get(networkNodeProperties.getCIProperty());
final Integer lac = (Integer)properties.get(networkNodeProperties.getLACProperty());
result = createSector(parent, name, lac, ci, properties);
} else if (elementType.equals(NetworkElementType.SITE)) {
final Double lat = (Double)properties.get(getGeoNodeProperties().getLatitudeProperty());
final Double lon = (Double)properties.get(getGeoNodeProperties().getLongitudeProperty());
result = createSite(parent, name, lat, lon, properties);
} else {
result = createDefaultElement(elementType, parent, name, properties);
}
updateSynonyms(elementType, properties);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getFinishLogStatement("createElement"));
}
return result;
}
protected IDataElement createSector(final IDataElement parent, final String name, final Integer lac, final Integer ci,
final Map<String, Object> properties) throws ModelException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getStartLogStatement("createSector", parent, name, lac, ci, properties));
}
final DataElement result = createDefaultElement(NetworkElementType.SECTOR, parent, name, properties);
if (result != null) {
if (ci != null) {
getIndexModel().index(NetworkElementType.SECTOR, result.getNode(), networkNodeProperties.getCIProperty(), ci);
}
if (lac != null) {
getIndexModel().index(NetworkElementType.SECTOR, result.getNode(), networkNodeProperties.getLACProperty(), lac);
}
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getFinishLogStatement("createSector"));
}
return result;
}
protected IDataElement createSite(final IDataElement parent, final String name, final Double latitude, final Double longitude,
final Map<String, Object> properties) throws ModelException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getStartLogStatement("createSite", parent, name, latitude, longitude, properties));
}
// validate input
if (latitude == null) {
throw new ParameterInconsistencyException(getGeoNodeProperties().getLatitudeProperty());
}
if (longitude == null) {
throw new ParameterInconsistencyException(getGeoNodeProperties().getLongitudeProperty());
}
final DataElement result = createDefaultElement(NetworkElementType.SITE, parent, name, properties);
if (result != null) {
getIndexModel().indexInMultiProperty(NetworkElementType.SITE, result.getNode(), Double.class,
getGeoNodeProperties().getLatitudeProperty(), getGeoNodeProperties().getLongitudeProperty());
updateLocation(latitude, longitude);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getFinishLogStatement("createSite"));
}
return result;
}
@Override
public IDataElement findElement(final INetworkElementType elementType, final String elementName) throws ModelException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getStartLogStatement("findElement", elementType, elementName));
}
// validate input
if (elementType == null) {
throw new ParameterInconsistencyException("elementType");
}
if (StringUtils.isEmpty(elementName)) {
throw new ParameterInconsistencyException("elementName", elementName);
}
IDataElement result = null;
final Node elementNode = getIndexModel().getSingleNode(elementType, getGeneralNodeProperties().getNodeNameProperty(),
elementName);
if (elementNode != null) {
result = new DataElement(elementNode);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getFinishLogStatement("findElement"));
}
return result;
}
@SuppressWarnings("unchecked")
@Override
public IDataElement findSector(final String sectorName, final Integer ci, final Integer lac) throws ModelException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getStartLogStatement("findSector", sectorName, ci, lac));
}
// validate input
if (StringUtils.isEmpty(sectorName) && ci == null && lac == null) {
throw new ParameterInconsistencyException(getGeneralNodeProperties().getNodeNameProperty(), sectorName);
}
if (ci == null && StringUtils.isEmpty(sectorName)) {
throw new ParameterInconsistencyException(networkNodeProperties.getCIProperty(), ci);
}
IDataElement result = null;
if (!StringUtils.isEmpty(sectorName)) {
result = findElement(NetworkElementType.SECTOR, sectorName);
}
if (result == null && ci != null) {
final List<Node> ciList = getNodeListFromIndex(NetworkElementType.SECTOR, networkNodeProperties.getCIProperty(), ci);
List<Node> resultList = null;
if (lac != null) {
final List<Node> lacNodes = getNodeListFromIndex(NetworkElementType.SECTOR, networkNodeProperties.getLACProperty(),
lac);
resultList = new ArrayList<Node>(CollectionUtils.intersection(ciList, lacNodes));
}
if (resultList != null && !resultList.isEmpty()) {
result = new DataElement(resultList.get(0));
} else if (!ciList.isEmpty() && lac == null) {
result = new DataElement(ciList.get(0));
}
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getFinishLogStatement("findElement"));
}
return result;
}
@Override
public void finishUp() throws ModelException {
LOGGER.info("Finishing up model <" + getName() + ">");
try {
initializeSynonyms();
getNodeService().updateProperties(synonymNode, synonyms);
getNodeService().updateProperty(getRootNode(), networkNodeProperties.getStuctureProperty(),
structureToStringArrayFormat());
} catch (final ServiceException e) {
processException("can't set structure properties", e);
}
super.finishUp();
}
@Override
public Iterable<IDataElement> getAllElementsByType(final INodeType nodeType) throws ModelException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getStartLogStatement("getAllElementsByType", nodeType));
}
final Iterable<IDataElement> result = new DataElementIterator(getIndexModel().getAllNodes(nodeType)).toIterable();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getFinishLogStatement("getAllElementsByType"));
}
return result;
}
@Override
public ILocationElement getElementLocation(final IDataElement dataElement) throws ModelException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getStartLogStatement("getElelemntLocation", dataElement));
}
ILocationElement location = null;
final Node elementNode = ((DataElement)dataElement).getNode();
try {
if (dataElement instanceof ISiteElement) {
location = (ISiteElement)dataElement;
} else if (dataElement.getNodeType().equals(NetworkElementType.SITE)) {
location = getLocationElement(elementNode);
} else if (dataElement.getNodeType().equals(NetworkElementType.SECTOR)) {
location = getLocationElement(getParent(elementNode));
}
} catch (final ServiceException e) {
processException("Error on computing Location element", e);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getFinishLogStatement("getElementLocation"));
}
return location;
}
@Override
public Iterable<ILocationElement> getElements(final Envelope bound) throws ModelException {
final Double[] min = new Double[] {bound.getMinY(), bound.getMinX()};
final Double[] max = new Double[] {bound.getMaxY(), bound.getMaxX()};
final Iterator<Node> nodeIterator = getIndexModel().getNodes(NetworkElementType.SITE, Double.class, min, max,
getGeoNodeProperties().getLatitudeProperty(), getGeoNodeProperties().getLongitudeProperty());
return new LocationIterator(nodeIterator).toIterable();
}
@Override
public Iterable<ILocationElement> getElements(final Envelope bound, final IFilter filter) {
// TODO Auto-generated method stub
return null;
}
@SuppressWarnings("unchecked")
@Override
public Iterable<ILocationElement> getElementsLocations(final Iterable<IDataElement> dataElements) {
Iterable<ILocationElement> result = (Iterable<ILocationElement>)elementLocationsIteratorCache.get(dataElements);
if (result == null) {
result = new ElementLocationIterator(dataElements).toIterable();
elementLocationsIteratorCache.put(dataElements, result);
} else {
result = new ElementLocationIterator(dataElements).toIterable();
}
return result;
}
@Override
protected ILocationElement getLocationElement(final Node node) {
final SiteElement site = new SiteElement(node);
site.setNodeType(NetworkElementType.SITE);
try {
site.setLatitude((Double)getNodeService().getNodeProperty(node, getGeoNodeProperties().getLatitudeProperty(), null,
true));
site.setLongitude((Double)getNodeService().getNodeProperty(node, getGeoNodeProperties().getLongitudeProperty(), null,
true));
final Iterator<Node> sectorNodes = getNodeService().getChildren(node, NetworkElementType.SECTOR,
NodeServiceRelationshipType.CHILD);
while (sectorNodes.hasNext()) {
site.addSector(getSectorElement(sectorNodes.next()));
}
} catch (final ServiceException e) {
LOGGER.error("Unable to create a SiteElement from node", e);
return null;
}
return site;
}
@Override
protected INodeType getModelType() {
return NetworkElementType.NETWORK;
}
@Override
public INodeType[] getNetworkStructure() {
return structure.toArray(new INodeType[structure.size()]);
}
private List<Node> getNodeListFromIndex(final INodeType nodeType, final String propertyName, final Object value)
throws ModelException {
final List<Node> result = new ArrayList<Node>();
CollectionUtils.addAll(result, getIndexModel().getNodes(nodeType, propertyName, value));
return result;
}
@Override
public int getRenderableElementCount() {
return getPropertyStatistics().getCount(NetworkElementType.SITE);
}
private ISectorElement getSectorElement(final Node node) throws ServiceException {
final SectorElement element = new SectorElement(node);
element.setNodeType(NetworkElementType.SECTOR);
element.setName(getNodeService().getNodeName(node));
element.setAzimuth((Double)getNodeService().getNodeProperty(node, networkNodeProperties.getAzimuthProperty(), null, false));
element.setBeamwidth((Double)getNodeService().getNodeProperty(node, networkNodeProperties.getBeamwidthProperty(), null,
false));
return element;
}
@Override
public Map<String, String> getSynonyms() {
return synonyms;
}
@Override
public void initialize(final Node rootNode) throws ModelException {
super.initialize(rootNode);
initializeNetworkStructure();
initializeSynonyms();
}
/**
* @param rootNode
* @throws NodeTypeNotExistsException
*/
public void initializeNetworkStructure() {
structure.clear();
final String[] structure = (String[])getRootNode().getProperty(networkNodeProperties.getStuctureProperty());
for (final String element : structure) {
INodeType type;
try {
type = NodeTypeManager.getInstance().getType(element);
} catch (final NodeTypeNotExistsException e) {
LOGGER.error("can't find node type " + element, e);
continue;
}
this.structure.add(type);
}
}
/**
* @param node
* @throws ModelException
*/
private void initializeSynonyms() throws ModelException {
if (synonymNode != null) {
return;
}
Iterator<Node> nodes;
try {
nodes = getNodeService().getChildren(getRootNode(), NetworkElementType.SYNONYMS, NetworkRelationshipTypes.SYNONYMS);
if (nodes.hasNext()) {
synonymNode = nodes.next();
} else {
synonymNode = getNodeService().createNode(getRootNode(), NetworkElementType.SYNONYMS,
NetworkRelationshipTypes.SYNONYMS);
}
} catch (ServiceException e) {
processException("can't get children from node " + getRootNode(), e);
}
synonyms = new HashMap<String, String>();
if (synonymNode == null) {
return;
}
for (String key : synonymNode.getPropertyKeys()) {
if (key.equals(getGeneralNodeProperties().getNodeTypeProperty())) {
continue;
}
synonyms.put(key, (String)synonymNode.getProperty(key));
}
}
@Override
protected boolean isInAppropriatedProperty(final String propertyName) {
return !getGeoNodeProperties().getLatitudeProperty().equals(propertyName)
&& !getGeoNodeProperties().getLongitudeProperty().equals(propertyName);
}
protected Map<String, Object> removeIgnoredProperties(final Map<String, Object> properties) {
properties.remove(getGeoNodeProperties().getLatitudeProperty());
properties.remove(getGeoNodeProperties().getLongitudeProperty());
return properties;
}
@Override
public IDataElement replaceChild(final IDataElement child, final IDataElement newParent) throws ModelException {
// TODO Auto-generated method stub
return null;
}
public void setStructure(final List<INodeType> structure) throws ModelException {
this.structure = structure;
try {
getNodeService().updateProperty(getRootNode(), networkNodeProperties.getStuctureProperty(),
structureToStringArrayFormat());
} catch (final ServiceException e) {
processException("can't setStructure to network", e);
}
}
private String[] structureToStringArrayFormat() {
final String[] structure = new String[this.structure.size()];
int i = 0;
for (final INodeType type : this.structure) {
structure[i] = type.getId();
i++;
}
return structure;
}
@Override
protected void updateIndexModel(final IDataElement element, final String propertyName, final Object propertyValue)
throws ModelException {
if (element.getNodeType().equals(NetworkElementType.SECTOR)) {
updateSectorIndex(element, propertyName, propertyValue);
} else if (element.getNodeType().equals(NetworkElementType.SITE)) {
updateSiteIndex(element, propertyName, propertyValue);
} else if (getGeneralNodeProperties().getNodeNameProperty().equals(propertyName)) {
getIndexModel().updateIndex(element.getNodeType(), ((DataElement)element).getNode(),
getGeneralNodeProperties().getNodeNameProperty(), element.get(propertyName), propertyValue);
}
}
/**
* @param parent
* @param elementType
* @throws ServiceException
*/
private void updateNetworkStructure(final IDataElement parent, final INetworkElementType elementType) throws ServiceException {
INodeType parentType;
try {
parentType = getNodeService().getNodeType(((DataElement)parent).getNode());
} catch (final ServiceException e) {
throw e;
} catch (final NodeTypeNotExistsException e) {
LOGGER.error("can't get parent node type", e);
return;
}
final INodeType currentType = elementType;
final int parentIndex = structure.indexOf(parentType);
final int currentIndex = structure.indexOf(currentType);
if (currentIndex < 0) {
structure.add(currentType);
return;
} else if (currentIndex > 0 && parentIndex >= 0) {
return;
}
final int lastIndex = structure.indexOf(NetworkElementType.SITE);
if (parentIndex < lastIndex) {
structure.add(parentIndex, currentType);
}
}
/**
* @param element
* @param propertyName
* @param propertyValue
* @throws ModelException
*/
private void updateSectorIndex(final IDataElement element, final String propertyName, final Object propertyValue)
throws ModelException {
final DataElement sectorElement = (DataElement)element;
final Node sectorNode = sectorElement.getNode();
if (propertyName.equals(networkNodeProperties.getCIProperty())) {
getIndexModel().updateIndex(NetworkElementType.SECTOR, sectorNode, networkNodeProperties.getCIProperty(),
sectorElement.get(networkNodeProperties.getCIProperty()), propertyValue);
} else if (propertyName.equals(networkNodeProperties.getLACProperty())) {
getIndexModel().updateIndex(NetworkElementType.SECTOR, sectorNode, networkNodeProperties.getLACProperty(),
sectorElement.get(networkNodeProperties.getLACProperty()), propertyValue);
}
}
/**
* @param element
* @param propertyName
* @param propertyValue
* @throws ModelException
*/
private void updateSiteIndex(final IDataElement element, final String propertyName, final Object propertyValue)
throws ModelException {
final Double lat = (Double)element.get(getGeoNodeProperties().getLatitudeProperty());
final Double lon = (Double)element.get(getGeoNodeProperties().getLongitudeProperty());
final Node siteNode = ((DataElement)element).getNode();
getIndexModel().indexInMultiProperty(NetworkElementType.SITE, siteNode, Double.class,
getGeoNodeProperties().getLatitudeProperty(), getGeoNodeProperties().getLongitudeProperty());
if (getGeoNodeProperties().getLatitudeProperty().equals(propertyName)) {
updateLocation((Double)propertyValue, lon);
} else if (getGeoNodeProperties().getLongitudeProperty().equals(propertyName)) {
updateLocation(lat, (Double)propertyValue);
}
}
/**
* @param elementType
* @param properties
* @throws ParameterInconsistencyException
*/
private void updateSynonyms(final INetworkElementType elementType, final Map<String, Object> properties)
throws ParameterInconsistencyException {
if (synonyms == null) {
return;
}
// validate input
if (elementType == null) {
throw new ParameterInconsistencyException("elementType");
}
if (properties == null) {
throw new ParameterInconsistencyException("properties", null);
}
String typeId = elementType.getId();
for (String property : properties.keySet()) {
String key = String.format(SYNONYMS_KEY_FORMAT, typeId, property);
if (!synonyms.containsKey(key) && !property.equals(getGeneralNodeProperties().getNodeTypeProperty())) {
if (property.equals(getGeneralNodeProperties().getNodeNameProperty())) {
synonyms.put(key, elementType.getId().toUpperCase());
} else {
synonyms.put(key, property);
}
}
}
}
@Override
public void updateSynonyms(final Map<String, Object> synonymnsMap) throws ModelException {
assert synonymnsMap != null;
initializeSynonyms();
for (Entry<String, Object> synonym : synonymnsMap.entrySet()) {
synonyms.put(synonym.getKey(), (String)synonym.getValue());
}
}
}