ClimateDataServiceImpl.java

/*
 * Copyright 2018 Global Crop Diversity Trust
 *
 * 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 org.genesys.server.service.impl;

import java.lang.reflect.InvocationTargetException;
import java.nio.MappedByteBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.genesys.server.api.model.WorldclimJson;
import org.genesys.server.model.genesys.AccessionId;
import org.genesys.server.model.impl.TileClimate;
import org.genesys.server.persistence.AccessionIdRepository;
import org.genesys.server.persistence.TileClimateRepository;
import org.genesys.server.service.ClimateDataService;
import org.genesys.worldclim.WorldClimUtil;
import org.genesys.worldclim.grid.generic.Header;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * The ClimateDataService implementation.
 */
@Service
@Transactional(readOnly = true)
public class ClimateDataServiceImpl implements ClimateDataService {
	public static final Logger LOG = LoggerFactory.getLogger(ClimateDataServiceImpl.class);
	
	/** The tile climate repository. */
	@Autowired
	private TileClimateRepository tileClimateRepository;

	/** The accessionId repository. */
	@Autowired
	private AccessionIdRepository accessionIdRepository;

	/* (non-Javadoc)
	 * @see org.genesys.server.service.DSService#updateAccessionTileIndex(java.util.List)
	 */
	@Override
	@Transactional
	public void updateAccessionTileIndex(List<AccessionId> toSave) {
		accessionIdRepository.saveAll(toSave);
	}

	/* (non-Javadoc)
	 * @see org.genesys.server.service.ClimateDataService#worldclimUpdate(java.lang.String, java.util.HashSet, java.nio.MappedByteBuffer, short, double)
	 */
	@Override
	@Transactional(timeout = 30)
	public void worldclimUpdate(String variableName, HashSet<Long> tileIndexes, MappedByteBuffer buffer, Header header, double factor) {
		LOG.debug("Updating {} for tileIndexes: {}", variableName, tileIndexes.size());

		short nullValue = (short) header.getNoDataValue();
		
		Map<Long, TileClimate> rowMap = findOrMakeRows(tileIndexes);

		for (final Long genesysTileIndex : tileIndexes) {
			if (genesysTileIndex == null) {
				continue;
			}

			TileClimate tileClimate = rowMap.get(genesysTileIndex);
			if (tileClimate == null) {
				LOG.warn("No TileClimate for {}", genesysTileIndex);
				continue;
			}

			Long bilTileIndex = WorldClimUtil.tileIndexToDataIndex(genesysTileIndex); 
			
			if (bilTileIndex == null) {
				LOG.debug("Antarctica not covered");
				rowMap.remove(genesysTileIndex);
			} else if (bilTileIndex * 2 > buffer.capacity() - 2) {
				LOG.info("OUT OF FILE tile={}", bilTileIndex);
				rowMap.remove(genesysTileIndex);
			} else {
				try {
					short val = buffer.getShort((int) (bilTileIndex * 2));
					if (val != nullValue) {
						LOG.trace("tile={} val={}", bilTileIndex, val);

						Double value = null;
						if (factor == 1.0) {
							value = Double.valueOf((long) (factor * val));
						} else {
							value = Double.valueOf(factor * val);
						}

						try {
							var current = PropertyUtils.getProperty(tileClimate, variableName);
							if (current == null || !current.equals(value)) {
								BeanUtils.setProperty(tileClimate, variableName, value);
							} else {
								rowMap.remove(genesysTileIndex); // Need to update
							}
						} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
							LOG.error("Cannot set TileClimate#{} to {}. {}", variableName, value, e.getMessage());
						}
					} else {
						// don't save in case of NODATA
						rowMap.remove(genesysTileIndex);
					}
				} catch (IndexOutOfBoundsException e) {
					LOG.error("OUT OF BOUND genesysTile={} dataTile={} capacity={} limit={}", genesysTileIndex, bilTileIndex, buffer.capacity(), buffer.limit());
					throw e;
				}
			}
		}

		if (rowMap.size() > 0) {
			LOG.debug("Updating {} records of {}", rowMap.size(), variableName);
			tileClimateRepository.saveAll(rowMap.values());
		}
		LOG.debug("Done processing variable {}. {} records updated.", variableName, rowMap.size());
	}

	/**
	 * Find or make rows.
	 *
	 * @param tileIndexes the tile indexes
	 * @return the map
	 */
	private Map<Long, TileClimate> findOrMakeRows(HashSet<Long> tileIndexes) {
		List<TileClimate> existingTiles = tileClimateRepository.findAllById(tileIndexes);
		HashMap<Long, TileClimate> map = new HashMap<>(tileIndexes.size());
		existingTiles.forEach((tile) -> {
			map.put(tile.getTileIndex(), tile);
		});
		tileIndexes.forEach(tileIndex -> {
			if (tileIndex != null && !map.containsKey(tileIndex)) {
				map.put(tileIndex, new TileClimate(tileIndex));
			}
		});
		return map;
	}

	/* (non-Javadoc)
	 * @see org.genesys.server.service.ClimateDataService#climateForTile(java.lang.Long)
	 */
	@Override
	public TileClimate climateForTile(Long tileIndex) {
		return tileClimateRepository.findById(tileIndex).orElse(null);
	}

	/* (non-Javadoc)
	 * @see org.genesys.server.service.ClimateDataService#jsonForTile(java.lang.Long)
	 */
	@Override
	public WorldclimJson jsonForTile(Long tileIndex) {
		TileClimate tileClimate = tileClimateRepository.findById(tileIndex).orElse(null);
		if (tileClimate == null)
			return null;

		WorldclimJson wc = new WorldclimJson();
		wc.setPrecipitation(new Double[] { tileClimate.getPrec1(), tileClimate.getPrec2(), tileClimate.getPrec3(), tileClimate.getPrec4(), tileClimate.getPrec5(), tileClimate.getPrec6(), tileClimate.getPrec7(), tileClimate.getPrec8(), tileClimate.getPrec9(), tileClimate.getPrec10(), tileClimate.getPrec11(), tileClimate.getPrec12() } );
		wc.setTempMin(new Double[] { tileClimate.getTmin1(), tileClimate.getTmin2(), tileClimate.getTmin3(), tileClimate.getTmin4(), tileClimate.getTmin5(), tileClimate.getTmin6(), tileClimate.getTmin7(), tileClimate.getTmin8(), tileClimate.getTmin9(), tileClimate.getTmin10(), tileClimate.getTmin11(), tileClimate.getTmin12() } );
		wc.setTempMean(new Double[] { tileClimate.getTmean1(), tileClimate.getTmean2(), tileClimate.getTmean3(), tileClimate.getTmean4(), tileClimate.getTmean5(), tileClimate.getTmean6(), tileClimate.getTmean7(), tileClimate.getTmean8(), tileClimate.getTmean9(), tileClimate.getTmean10(), tileClimate.getTmean11(), tileClimate.getTmean12() } );
		wc.setTempMax(new Double[] { tileClimate.getTmax1(), tileClimate.getTmax2(), tileClimate.getTmax3(), tileClimate.getTmax4(), tileClimate.getTmax5(), tileClimate.getTmax6(), tileClimate.getTmax7(), tileClimate.getTmax8(), tileClimate.getTmax9(), tileClimate.getTmax10(), tileClimate.getTmax11(), tileClimate.getTmax12() } );
	
		return wc;
	}

}