GeographyTriggers.java
/*
* Copyright 2026 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.gringlobal.service.triggers;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.CaseBuilder;
import com.querydsl.core.types.dsl.NumberExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.gringlobal.model.Geography;
import org.gringlobal.model.GeographyRegionMap;
import org.gringlobal.model.QGeography;
import org.gringlobal.model.QGeographyRegionMap;
import org.gringlobal.persistence.GeographyRegionMapRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Aspect
@Component("GeographyTriggers")
@Slf4j
public class GeographyTriggers {
@Autowired
private JPAQueryFactory jpaQueryFactory;
@Autowired
private GeographyRegionMapRepository geographyRegionMapRepository;
@Pointcut("execution(* org.gringlobal.persistence.GeographyRepository.save(..)) || execution(* org.gringlobal.persistence.GeographyRepository.saveAndFlush(..))")
public void saveGeography() {
}
@Pointcut("execution(* org.gringlobal.persistence.GeographyRepository.saveAll(Iterable)) || execution(* org.gringlobal.persistence.GeographyRepository.saveAllAndFlush(Iterable))")
public void saveGeographies() {
}
@Pointcut("execution(* org.gringlobal.persistence.GeographyRepository.delete(..)) || execution(* org.gringlobal.persistence.GeographyRepository.deleteAll(..))")
public void removeGeographies() {
}
@Pointcut("execution(* org.gringlobal.persistence.GeographyRegionMapRepository.delete(..)) || execution(* org.gringlobal.persistence.GeographyRegionMapRepository.deleteAll(..))")
public void removeGeographyRegionMappings() {
}
@After(value = "saveGeography() || saveGeographies()")
public void afterGeographiesSave(final JoinPoint joinPoint) {
var result = joinPoint.getArgs()[0];
if (result instanceof Collection) {
((Collection<Geography>) result).forEach(this::assignGeographyRegionIfExists);
} else {
assignGeographyRegionIfExists((Geography) result);
}
}
private void assignGeographyRegionIfExists(Geography geography) {
var qGeography = QGeography.geography;
var qGeographyRegionMap = QGeographyRegionMap.geographyRegionMap;
BooleanExpression base = qGeography.countryCode.eq(geography.getCountryCode());
List<BooleanExpression> levels = new ArrayList<>();
if (geography.getAdm4() != null) {
levels.add(base
.and(qGeography.adm1.eq(geography.getAdm1())).and(qGeography.adm2.eq(geography.getAdm2()))
.and(qGeography.adm3.eq(geography.getAdm3())).and(qGeography.adm4.isNull()));
}
if (geography.getAdm3() != null) {
levels.add(base
.and(qGeography.adm1.eq(geography.getAdm1())).and(qGeography.adm2.eq(geography.getAdm2()))
.and(qGeography.adm3.isNull()).and(qGeography.adm4.isNull()));
}
if (geography.getAdm2() != null) {
levels.add(base
.and(qGeography.adm1.eq(geography.getAdm1())).and(qGeography.adm2.isNull())
.and(qGeography.adm3.isNull()).and(qGeography.adm4.isNull()));
}
if (geography.getAdm1() != null) {
levels.add(base
.and(qGeography.adm1.isNull()).and(qGeography.adm2.isNull())
.and(qGeography.adm3.isNull()).and(qGeography.adm4.isNull()));
}
NumberExpression<Integer> levelOrder = new CaseBuilder()
// parent of adm4
.when(qGeography.adm4.isNull().and(qGeography.adm3.isNotNull())).then(3)
// parent of adm3
.when(qGeography.adm3.isNull().and(qGeography.adm2.isNotNull())).then(2)
// parent of adm2
.when(qGeography.adm2.isNull().and(qGeography.adm1.isNotNull())).then(1)
// country level
.otherwise(0);
if (levels.isEmpty()) {
return; // country level
}
BooleanExpression levelsExpression = levels.stream().reduce(BooleanExpression::or).orElse(null);
var region = jpaQueryFactory
.select(qGeographyRegionMap.region()).from(qGeographyRegionMap)
.join(qGeographyRegionMap.geography(), qGeography)
.where(levelsExpression)
.orderBy(levelOrder.desc())
.fetchFirst();
if (region == null) return;
GeographyRegionMap mapToSave = new GeographyRegionMap();
mapToSave.setGeography(geography);
mapToSave.setRegion(region);
geographyRegionMapRepository.saveAndFlush(mapToSave);
}
@Around(value = "removeGeographies() && args(geographyInput)")
public Object aroundGeographiesDelete(final ProceedingJoinPoint joinPoint, Object geographyInput) throws Throwable {
if (geographyInput instanceof Geography) {
removeGeographyRegionMappings((Geography) geographyInput, null);
} else if (geographyInput instanceof Collection) {
((Collection<Geography>) geographyInput).forEach(geo -> removeGeographyRegionMappings(geo, null));
}
return joinPoint.proceed();
}
@Around(value = "removeGeographyRegionMappings() && args(geographyMapInput)")
public Object aroundGeographyRegionMapDelete(final ProceedingJoinPoint joinPoint, Object geographyMapInput) throws Throwable {
if (geographyMapInput instanceof GeographyRegionMap) {
removeGeographyRegionMappings(((GeographyRegionMap) geographyMapInput).getGeography(), List.of(((GeographyRegionMap) geographyMapInput).getId()));
} else if (geographyMapInput instanceof Collection) {
var sourceIds = ((Collection<GeographyRegionMap>) geographyMapInput).stream().map(GeographyRegionMap::getId).collect(Collectors.toList());
((Collection<GeographyRegionMap>) geographyMapInput).stream()
.forEach(map -> removeGeographyRegionMappings(map.getGeography(), sourceIds));
}
return joinPoint.proceed();
}
private void removeGeographyRegionMappings(Geography geography, List<Long> sourceIds) {
QGeography qGeography = QGeography.geography;
BooleanExpression predicate = qGeography.countryCode.eq(geography.getCountryCode());
if (geography.getAdm1() != null) predicate = predicate.and(qGeography.adm1.eq(geography.getAdm1()));
if (geography.getAdm2() != null) predicate = predicate.and(qGeography.adm2.eq(geography.getAdm2()));
if (geography.getAdm3() != null) predicate = predicate.and(qGeography.adm3.eq(geography.getAdm3()));
if (geography.getAdm4() != null) predicate = predicate.and(qGeography.adm4.eq(geography.getAdm4()));
var geographiesToRemoveMappingsQuery = jpaQueryFactory.select(qGeography).from(qGeography).where(predicate);
var whereExpr = QGeographyRegionMap.geographyRegionMap.geography().in(geographiesToRemoveMappingsQuery);
if (!CollectionUtils.isEmpty(sourceIds)) {
whereExpr = whereExpr.and(QGeographyRegionMap.geographyRegionMap.id.notIn(sourceIds));
}
jpaQueryFactory.delete(QGeographyRegionMap.geographyRegionMap)
.where(whereExpr)
.execute();
}
}