GeoRegionServiceImpl.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.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import javax.xml.parsers.ParserConfigurationException;

import org.genesys.server.exception.NotFoundElement;
import org.genesys.server.model.impl.Country;
import org.genesys.server.model.impl.GeoRegion;
import org.genesys.server.model.impl.QGeoRegion;
import org.genesys.server.persistence.CountryRepository;
import org.genesys.server.persistence.GeoRegionRepository;
import org.genesys.server.service.GeoRegionService;
import org.genesys.server.service.worker.GeoRegionDataCLDR;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.xml.sax.SAXException;

import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;

/**
 * GeoRegion service.
 */
@Service
@Transactional(readOnly = true)
public class GeoRegionServiceImpl implements GeoRegionService {

	/** The Constant LOG. */
	private static final Logger LOG = LoggerFactory.getLogger(GeoRegionServiceImpl.class);

	/** The country repository. */
	@Autowired
	CountryRepository countryRepository;

	/** The geo region repository. */
	@Autowired
	GeoRegionRepository geoRegionRepository;

	/** The geo region data CLDR. */
	@Autowired
	GeoRegionDataCLDR geoRegionDataCLDR;

	/** The jpa query factory. */
	@Autowired
	private JPAQueryFactory jpaQueryFactory;

	/* (non-Javadoc)
	 * @see org.genesys.server.service.GeoRegionService#find(java.lang.String)
	 */
	@Override
	public GeoRegion find(String regionIsoCode) {
		GeoRegion geoRegion = geoRegionRepository.findByIsoCode(regionIsoCode);
		if (geoRegion == null) {
			throw new NotFoundElement("Can't find region for region code " + regionIsoCode);
		}
		geoRegion.getCountries().size();
		return geoRegion;
	}

	/* (non-Javadoc)
	 * @see org.genesys.server.service.GeoRegionService#save(org.genesys.server.model.impl.GeoRegion)
	 */
	@Override
	@Transactional(readOnly = false)
	public void save(GeoRegion geoRegion) {
		geoRegionRepository.save(geoRegion);
	}

	/* (non-Javadoc)
	 * @see org.genesys.server.service.GeoRegionService#delete(org.genesys.server.model.impl.GeoRegion)
	 */
	@Override
	@Transactional(readOnly = false)
	public void delete(GeoRegion geoRegion) {
		geoRegionRepository.delete(geoRegion);
	}

	/* (non-Javadoc)
	 * @see org.genesys.server.service.GeoRegionService#getRegion(org.genesys.server.model.impl.Country)
	 */
	@Override
	public GeoRegion getRegion(Country country) {
		// NPE check
		country.getId();

		GeoRegion geoRegion = geoRegionRepository.findByCountry(country);
		if (geoRegion != null) {
			geoRegion.getCountries().size();
		}
		return geoRegion;
	}

	@Override
	public GeoRegion get(long id) {
		return geoRegionRepository.findById(id).orElseThrow(() -> {
			throw new NotFoundElement("No region with id=" + id);
		});
	}

	/* (non-Javadoc)
	 * @see org.genesys.server.service.GeoRegionService#getChildren(java.lang.String)
	 */
	@Override
	public List<GeoRegion> getChildren(String regionIsoCode) {
		List<GeoRegion> all = findAll();
		GeoRegion region = find(regionIsoCode);
		List<GeoRegion> geoRegions = new ArrayList<>();

		geoRegions.addAll(all.stream().filter(reg -> isChild(reg, region.getIsoCode())).collect(Collectors.toList()));

		return geoRegions;
	}

	/* (non-Javadoc)
	 * @see org.genesys.server.service.GeoRegionService#findAll()
	 */
	@Override
	public List<GeoRegion> findAll() {
		return geoRegionRepository.findAll();
	}

	/* (non-Javadoc)
	 * @see org.genesys.server.service.GeoRegionService#findAll(java.util.Locale)
	 */
	@Override
	public List<GeoRegion> findAll(final Locale locale) {
		final List<GeoRegion> all = findAll();
		Collections.sort(all, new Comparator<GeoRegion>() {
			@Override
			public int compare(GeoRegion o1, GeoRegion o2) {
				return o1.getName(locale).compareTo(o2.getName(locale));
			}
		});
		return all;
	}

	/* (non-Javadoc)
	 * @see org.genesys.server.service.GeoRegionService#updateGeoRegionData()
	 */
	@Override
	@Transactional(readOnly = false)
	public void updateGeoRegionData() throws IOException, ParserConfigurationException, SAXException {
		// update current geoRegions
		if (geoRegionDataCLDR == null) {
			LOG.warn("unicode.org geoRegions source not available");
			return;
		}

		List<GeoRegion> existingRegions = geoRegionRepository.findAll();
		for (final GeoRegion src : geoRegionDataCLDR.getGeoRegionDataCLDR()) {

			GeoRegion geoRegionForUpdate = null;
			Optional<GeoRegion> match = existingRegions.stream().filter(r -> r.getIsoCode().equals(src.getIsoCode())).findFirst();
			if (match.isPresent()) {
				geoRegionForUpdate = match.get();
			} else {
				geoRegionForUpdate = new GeoRegion();
				geoRegionForUpdate.setIsoCode(src.getIsoCode());
			}
			geoRegionForUpdate.setName(src.getName());
			geoRegionForUpdate.setNameL(src.getNameL());
			geoRegionForUpdate.setCountries(src.getCountries());
			if (src.getParentRegion() == null) {
				geoRegionForUpdate.setParentRegion(null);
			} else {
				geoRegionForUpdate.setParentRegion(geoRegionRepository.findByIsoCode(src.getParentRegion().getIsoCode()));
			}

			geoRegionRepository.save(geoRegionForUpdate);

			if (null != geoRegionForUpdate.getCountries()) {
				for (Country country : geoRegionForUpdate.getCountries()) {
					if (null != country) {
						country.setRegion(geoRegionForUpdate);
						countryRepository.save(country);
					}
				}
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.genesys.server.service.GeoRegionService#getGeoRegionsForFilter()
	 */
	@Override
	public List<GeoRegion> getGeoRegionsForFilter() {
		List<GeoRegion> geoRegionList = findAll();
		geoRegionList.removeIf(geoRegion -> geoRegion.getName().equals("World") || geoRegion.getName().equals("Americas"));

		List<GeoRegion> resultList = new ArrayList<>();

		for (GeoRegion geoRegion : geoRegionList) {
			GeoRegion parentRegion = geoRegion.getParentRegion();
			if (parentRegion != null && (parentRegion.getName().equals("World") || parentRegion.getName().equals("Americas"))) {
				geoRegion.setParentRegion(null);
				resultList.add(geoRegion);
			} else
				resultList.add(geoRegion);
		}

		Locale locale = LocaleContextHolder.getLocale();
		resultList.sort(Comparator.comparing(o -> o.getName(locale)));

		return resultList;
	}
	
	/* (non-Javadoc)
	 * @see org.genesys.server.service.GeoRegionService#countryCodesFor(java.util.Set)
	 */
	@Override
	public Collection<String> countryCodesFor(Set<String> regionCodes) {
		JPAQuery<String> x = jpaQueryFactory.selectFrom(QGeoRegion.geoRegion)
				// Select ISO3 country codes
				.select(QGeoRegion.geoRegion.countries.any().code3)
				// distinct
				.distinct()
				// where 
				.where(QGeoRegion.geoRegion.isoCode.in(regionCodes).or(QGeoRegion.geoRegion.parentRegion().isoCode.in(regionCodes)));
		
		return x.fetch();
	}

	@Override
	public RegionDetails getDetails(final String isoCode) {
		GeoRegion region = find(isoCode);
		region.setCountries(region.getCountries().stream().filter(Country::isCurrent).collect(Collectors.toList()));
		List<GeoRegion> subRegions = getChildren(region.getIsoCode());
		return RegionDetails.from(region, subRegions);
	}

	/* (non-Javadoc)
	 * @see org.genesys.server.service.GeoRegionService#conversionToSubRegionsList(org.genesys.server.model.impl.GeoRegion)
	 */
	@Override
	public List<GeoRegion> conversionToSubRegionsList(final GeoRegion parentGeo) {
		List<GeoRegion> geoRegionList = findAll();
		List<GeoRegion> subRegionsList = new ArrayList<>();
		geoRegionList.removeIf(geoRegion -> geoRegion.getName().equals("World"));

		subRegionsList.addAll(geoRegionList.stream().filter(geoRegion -> parentGeo.getName().equals(geoRegion.getParentRegion().getName())).collect(Collectors.toList()));

		return subRegionsList;
	}

	/**
	 * Checks if is child.
	 *
	 * @param region the region
	 * @param parentIsoCode the parent iso code
	 * @return true, if is child
	 */
	private boolean isChild(GeoRegion region, String parentIsoCode) {

		return region.getParentRegion() != null && (region.getParentRegion().getIsoCode().equals(parentIsoCode) || isChild(region.getParentRegion(), parentIsoCode));

	}
}