/* 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.measurement;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
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.FileElement;
import org.amanzi.neo.impl.dto.LocationElement;
import org.amanzi.neo.impl.util.AbstractDataElementIterator;
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.measurement.IMeasurementModel;
import org.amanzi.neo.models.measurement.MeasurementNodeType;
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.IMeasurementNodeProperties;
import org.amanzi.neo.nodeproperties.ITimePeriodNodeProperties;
import org.amanzi.neo.nodetypes.INodeType;
import org.amanzi.neo.nodetypes.NodeTypeManager;
import org.amanzi.neo.services.INodeService;
import org.amanzi.neo.services.exceptions.ServiceException;
import org.amanzi.neo.services.impl.NodeService.MeasurementRelationshipType;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.neo4j.graphdb.Node;
import com.vividsolutions.jts.geom.Envelope;
/**
* TODO Purpose of
* <p>
* </p>
*
* @author Nikolay Lagutko (nikolay.lagutko@amanzitel.com)
* @since 1.0.0
*/
public abstract class AbstractMeasurementModel extends AbstractDatasetModel implements IMeasurementModel {
private final class ElementLocationIterable implements IDataElementIterator<ILocationElement> {
private final Iterator<IDataElement> dataElements;
private final Set<ILocationElement> locationElements = new HashSet<ILocationElement>();
private boolean moveToNext;
private ILocationElement nextElement;
public ElementLocationIterable(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) {
element = getElementLocation((DataElement)dataElements.next());
element = locationElements.contains(element) ? null : element;
}
if (element != null) {
locationElements.add(element);
}
return element;
} 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 ElementLocationIterable.this;
}
};
}
}
protected final class MeasurementIterator extends AbstractDataElementIterator<IDataElement> {
/**
* @param nodeIterator
*/
public MeasurementIterator(final Iterator<Node> nodeIterator) {
super(nodeIterator);
}
@Override
protected IDataElement createDataElement(final Node node) {
return convertToDataElement(node);
}
}
private static final Logger LOGGER = Logger.getLogger(AbstractMeasurementModel.class);
private static final INodeType DEFAULT_PRIMARY_TYPE = MeasurementNodeType.M;
private int locationCount;
private final ITimePeriodNodeProperties timePeriodNodeProperties;
private final IMeasurementNodeProperties measurementNodeProperties;
private long minTimestamp = Long.MAX_VALUE;
private long maxTimestamp = Long.MIN_VALUE;
private INodeType primaryType = DEFAULT_PRIMARY_TYPE;
/**
* @param nodeService
* @param generalNodeProperties
* @param geoNodeProperties
*/
protected AbstractMeasurementModel(final ITimePeriodNodeProperties timePeriodNodeProperties,
final IMeasurementNodeProperties measurementNodeProperties, final INodeService nodeService,
final IGeneralNodeProperties generalNodeProperties, final IGeoNodeProperties geoNodeProperties) {
super(nodeService, generalNodeProperties, geoNodeProperties);
this.timePeriodNodeProperties = timePeriodNodeProperties;
this.measurementNodeProperties = measurementNodeProperties;
}
@Override
public IDataElement addMeasurement(final IDataElement parent, final Map<String, Object> properties) throws ModelException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getStartLogStatement("addMeasurement", parent, properties));
}
// validate input
if (parent == null) {
throw new ParameterInconsistencyException("parent");
}
if (properties == null || properties.isEmpty()) {
throw new ParameterInconsistencyException("properties", properties);
}
IDataElement result = null;
try {
final DataElement parentElement = (DataElement)parent;
final Node parentNode = parentElement.getNode();
final Node measurementNode = getNodeService().createNodeInChain(parentNode, getMainMeasurementNodeType(), properties);
result = getDataElement(measurementNode, null, null);
getIndexModel().indexInMultiProperty(getMainMeasurementNodeType(), measurementNode, Long.class,
timePeriodNodeProperties.getTimestampProperty());
Object cellId = properties.get(measurementNodeProperties.getCellIdPropertyName());
if (cellId != null) {
getIndexModel().index(getMainMeasurementNodeType(), measurementNode,
measurementNodeProperties.getCellIdPropertyName(), cellId);
}
getPropertyStatisticsModel().indexElement(getMainMeasurementNodeType(), properties);
final Long timestamp = (Long)properties.get(timePeriodNodeProperties.getTimestampProperty());
updateTimestamp(timestamp);
} catch (final ServiceException e) {
processException("Error on adding Measurement", e);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getFinishLogStatement("addMeasurement"));
}
return result;
}
@Override
public void addToLocation(final IDataElement measurement, final ILocationElement location) throws ModelException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getStartLogStatement("addToLocation", measurement, location));
}
try {
final DataElement measurementElement = (DataElement)measurement;
final DataElement locationElement = (DataElement)location;
getNodeService().linkNodes(measurementElement.getNode(), locationElement.getNode(),
MeasurementRelationshipType.LOCATION);
} catch (final ServiceException e) {
processException("Error on adding Location to Measurement", e);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getFinishLogStatement("addToLocation"));
}
}
private IDataElement convertToDataElement(final Node node) {
IDataElement element = null;
try {
final INodeType type = getNodeService().getNodeType(node);
if (type.equals(getMainMeasurementNodeType())) {
final String name = getNodeService().getNodeProperty(node, measurementNodeProperties.getEventProperty(), null,
false);
element = getDataElement(node, type, name);
} else if (type.equals(MeasurementNodeType.FILE)) {
final String name = getNodeService().getNodeName(node);
final String path = getNodeService().getNodeProperty(node, measurementNodeProperties.getFilePathProperty(), null,
false);
element = getFileElement(node, name, path);
}
} catch (final Exception e) {
LOGGER.error("Error on converting Node to Measurement Element", e);
}
return element;
}
@Override
public ILocationElement createLocation(final IDataElement parent, final double latitude, final double longitude,
final long timestamp) throws ModelException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getStartLogStatement("createLocation", parent, latitude, longitude, timestamp));
}
// validate input
if (parent == null) {
throw new ParameterInconsistencyException("parent");
}
ILocationElement location = null;
try {
final DataElement measurementElement = (DataElement)parent;
final Node measurementNode = measurementElement.getNode();
final Map<String, Object> properties = new HashMap<String, Object>();
properties.put(getGeoNodeProperties().getLatitudeProperty(), latitude);
properties.put(getGeoNodeProperties().getLongitudeProperty(), longitude);
properties.put(timePeriodNodeProperties.getTimestampProperty(), timestamp);
final Node locationNode = getNodeService().createNode(measurementNode, MeasurementNodeType.MP,
MeasurementRelationshipType.LOCATION, properties);
getIndexModel().indexInMultiProperty(MeasurementNodeType.MP, locationNode, Double.class,
getGeoNodeProperties().getLatitudeProperty(), getGeoNodeProperties().getLongitudeProperty());
updateLocation(latitude, longitude);
location = getLocationElement(locationNode);
locationCount++;
} catch (final ServiceException e) {
processException("Exception on creating Location", e);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getFinishLogStatement("createLocation"));
}
return location;
}
@Override
public Iterable<IDataElement> findElementByProperty(final String propertyName, final Object value) throws ModelException {
assert !StringUtils.isEmpty(propertyName);
assert value != null;
final Iterator<Node> elementNode = getIndexModel().getNodes(getMainMeasurementNodeType(), propertyName, value);
if (elementNode == null) {
LOGGER.error("Can't find element with property: " + propertyName + " and value: " + value);
return null;
}
return new DataElementIterator(elementNode).toIterable();
}
@Override
public void finishUp() throws ModelException {
try {
getNodeService().updateProperty(getRootNode(), timePeriodNodeProperties.getMinTimestampProperty(), minTimestamp);
getNodeService().updateProperty(getRootNode(), timePeriodNodeProperties.getMaxTimestampProperty(), maxTimestamp);
getNodeService().updateProperty(getRootNode(), measurementNodeProperties.getPrimaryTypeProperty(), primaryType.getId());
getNodeService().updateProperty(getRootNode(), getGeoNodeProperties().getLocationCountProperty(), locationCount);
super.finishUp();
} catch (final ServiceException e) {
processException("Exception on finishin up Measurement Model", e);
}
}
@Override
public Iterable<IDataElement> getAllElementsByType(final INodeType nodeType) throws ModelException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getStartLogStatement("getAllElementsByType", nodeType));
}
Iterable<IDataElement> result = null;
try {
result = new MeasurementIterator(getNodeService().getChildrenChain(getRootNode(), nodeType)).toIterable();
} catch (final ServiceException e) {
processException("An error occured on search child for parent Element", e);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getFinishLogStatement("getAllElementsByType"));
}
return result;
}
@Override
public Iterable<IDataElement> getChildren(final IDataElement parentElement) throws ModelException {
assert parentElement != null;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getStartLogStatement("getChildren", parentElement));
}
Iterable<IDataElement> result = null;
try {
final DataElement parent = (DataElement)parentElement;
final Node parentNode = parent.getNode();
result = new MeasurementIterator(getNodeService().getChildrenChain(parentNode)).toIterable();
} catch (final ServiceException e) {
processException("An error occured on search child for parent Element", e);
}
return result;
}
protected ILocationElement getElementLocation(final DataElement dataElement) {
ILocationElement result = null;
try {
final Node locationNode = getNodeService().getSingleChild(dataElement.getNode(), MeasurementNodeType.MP,
MeasurementRelationshipType.LOCATION);
if (locationNode != null) {
result = getLocationElement(locationNode);
}
} catch (final ServiceException e) {
LOGGER.error("Error on calculating location Node", e);
}
return result;
}
@Override
public ILocationElement getElementLocation(final IDataElement dataElement) throws ModelException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getStartLogStatement("getElementLocation", dataElement));
}
ILocationElement location = null;
final Node elementNode = ((DataElement)dataElement).getNode();
try {
final Node locationNode = getNodeService().getSingleChild(elementNode, MeasurementNodeType.MP,
MeasurementRelationshipType.LOCATION);
if (locationNode != null) {
location = getLocationElement(locationNode);
}
} catch (final ServiceException e) {
processException("Error on computing element location", e);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getFinishLogStatement("getElementLocation"));
}
return location;
}
@Override
public Iterable<ILocationElement> getElements(final Envelope bound) throws ModelException {
// TODO: LN: 10.10.2012, validate input
final Double[] min = new Double[] {bound.getMinY(), bound.getMinX()};
final Double[] max = new Double[] {bound.getMaxY(), bound.getMaxX()};
final Iterator<Node> nodeIterator = getIndexModel().getNodes(MeasurementNodeType.MP, 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;
}
@Override
public Iterable<IDataElement> getElements(final long minTimestamp, final long maxTimestamp) throws ModelException {
final Long[] min = new Long[] {minTimestamp};
final Long[] max = new Long[] {maxTimestamp};
final Iterator<Node> nodeIterator = getIndexModel().getNodes(getMainMeasurementNodeType(), Long.class, min, max,
timePeriodNodeProperties.getTimestampProperty());
return new DataElementIterator(nodeIterator).toIterable();
}
@Override
public Iterable<ILocationElement> getElementsLocations(final Iterable<IDataElement> dataElements) {
return new ElementLocationIterable(dataElements).toIterable();
}
@Override
public IFileElement getFile(final IDataElement parent, final String name, final String path) throws ModelException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getStartLogStatement("getFile", parent, name, path));
}
// validate input
if (parent == null) {
throw new ParameterInconsistencyException("parent");
}
if (StringUtils.isEmpty(name)) {
throw new ParameterInconsistencyException(getGeneralNodeProperties().getNodeNameProperty(), name);
}
if (StringUtils.isEmpty(path)) {
throw new ParameterInconsistencyException(measurementNodeProperties.getFilePathProperty(), path);
}
IFileElement result = null;
try {
final DataElement parentElement = (DataElement)parent;
final Node parentNode = parentElement.getNode();
Node fileNode = getNodeService().getChildInChainByName(parentNode, name, MeasurementNodeType.FILE);
if (fileNode == null) {
final Map<String, Object> properties = new HashMap<String, Object>();
properties.put(getGeneralNodeProperties().getNodeNameProperty(), name);
properties.put(measurementNodeProperties.getFilePathProperty(), path);
fileNode = getNodeService().createNodeInChain(parentNode, MeasurementNodeType.FILE, properties);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Created new File Node by name <" + name + "> in Model <" + getName() + ">");
}
}
result = getFileElement(fileNode, name, path);
} catch (final ServiceException e) {
processException("Error on adding new File", e);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(getFinishLogStatement("getFile"));
}
return result;
}
protected IFileElement getFileElement(final Node node, final String name, final String path) {
final FileElement file = new FileElement(node);
file.setName(name);
file.setPath(path);
file.setNodeType(MeasurementNodeType.FILE);
return file;
}
@Override
protected ILocationElement getLocationElement(final Node node) {
final LocationElement location = new LocationElement(node);
location.setNodeType(MeasurementNodeType.MP);
try {
location.setLatitude((Double)getNodeService().getNodeProperty(node, getGeoNodeProperties().getLatitudeProperty(), null,
true));
location.setLongitude((Double)getNodeService().getNodeProperty(node, getGeoNodeProperties().getLongitudeProperty(),
null, true));
} catch (final ServiceException e) {
LOGGER.error("Unable to create a Location Element from node", e);
return null;
}
return location;
}
@Override
public INodeType getMainMeasurementNodeType() {
return primaryType;
}
@Override
public long getMaxTimestamp() {
return maxTimestamp;
}
protected IMeasurementNodeProperties getMeasurementNodeProperties() {
return measurementNodeProperties;
}
@Override
public long getMinTimestamp() {
return minTimestamp;
}
@Override
public int getRenderableElementCount() {
return locationCount;
}
@Override
public void initialize(final Node node) throws ModelException {
try {
super.initialize(node);
minTimestamp = getNodeService().getNodeProperty(node, timePeriodNodeProperties.getMinTimestampProperty(),
Long.MAX_VALUE, false);
maxTimestamp = getNodeService().getNodeProperty(node, timePeriodNodeProperties.getMaxTimestampProperty(),
Long.MIN_VALUE, false);
final String primaryTypeName = getNodeService().getNodeProperty(node,
measurementNodeProperties.getPrimaryTypeProperty(), DEFAULT_PRIMARY_TYPE.getId(), false);
primaryType = NodeTypeManager.getInstance().getType(primaryTypeName);
locationCount = getNodeService().getNodeProperty(node, getGeoNodeProperties().getLocationCountProperty(), 0, false);
} catch (final Exception e) {
processException("Error on initialization of Measurement Model", e);
}
}
private void updateTimestamp(final long timestamp) {
minTimestamp = Math.min(minTimestamp, timestamp);
maxTimestamp = Math.max(maxTimestamp, timestamp);
}
}