// Copyright © 2015 HSL <https://www.hsl.fi> // This program is dual-licensed under the EUPL v1.2 and AGPLv3 licenses. package fi.hsl.parkandride.back; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.querydsl.core.ResultTransformer; import com.querydsl.core.Tuple; import com.querydsl.core.dml.StoreClause; import com.querydsl.core.types.ConstantImpl; import com.querydsl.core.types.MappingProjection; import com.querydsl.core.types.dsl.ComparableExpression; import com.querydsl.core.types.dsl.NumberPath; import com.querydsl.core.types.dsl.SimpleExpression; import com.querydsl.sql.SQLExpressions; import com.querydsl.sql.dml.SQLInsertClause; import com.querydsl.sql.dml.SQLUpdateClause; import com.querydsl.sql.postgresql.PostgreSQLQuery; import com.querydsl.sql.postgresql.PostgreSQLQueryFactory; import fi.hsl.parkandride.back.sql.*; import fi.hsl.parkandride.core.back.FacilityHistoryRepository; import fi.hsl.parkandride.core.back.FacilityRepository; import fi.hsl.parkandride.core.domain.*; import fi.hsl.parkandride.core.service.TransactionalRead; import fi.hsl.parkandride.core.service.TransactionalWrite; import fi.hsl.parkandride.core.service.ValidationException; import org.geolatte.geom.Point; import org.joda.time.DateTime; import java.util.*; import java.util.Map.Entry; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkNotNull; import static com.querydsl.core.group.GroupBy.*; import static com.querydsl.spatial.GeometryExpressions.dwithin; import static fi.hsl.parkandride.back.GSortedSet.sortedSet; import static fi.hsl.parkandride.core.domain.CapacityType.*; import static fi.hsl.parkandride.core.domain.Sort.Dir.ASC; import static fi.hsl.parkandride.core.domain.Sort.Dir.DESC; import static fi.hsl.parkandride.core.domain.Usage.*; import static org.springframework.util.CollectionUtils.isEmpty; public class FacilityDao implements FacilityRepository { private static final Sort DEFAULT_SORT = new Sort("name.fi", ASC); private static final QFacility qFacility = QFacility.facility; private static final QFacilityAlias qAlias = QFacilityAlias.facilityAlias; private static final QPort qPort = QPort.port; private static final QFacilityService qService = QFacilityService.facilityService; private static final QFacilityPaymentMethod qPaymentMethod = QFacilityPaymentMethod.facilityPaymentMethod; private static final QUnavailableCapacity qUnavailableCapacity = QUnavailableCapacity.unavailableCapacity; private static final QPricing qPricing = QPricing.pricing; private static final MultilingualStringMapping statusDescriptionMapping = new MultilingualStringMapping(qFacility.statusDescriptionFi, qFacility.statusDescriptionSv, qFacility.statusDescriptionEn); private static final MultilingualStringMapping openingHoursInfoMapping = new MultilingualStringMapping(qFacility.openingHoursInfoFi, qFacility.openingHoursInfoSv, qFacility.openingHoursInfoEn); private static final MultilingualUrlMapping openingHoursUrlMapping = new MultilingualUrlMapping(qFacility.openingHoursUrlFi, qFacility.openingHoursUrlSv, qFacility.openingHoursUrlEn); private static final MultilingualStringMapping pricingPriceMapping = new MultilingualStringMapping(qPricing.priceFi, qPricing.priceSv, qPricing.priceEn); private static final AddressMapping addressMapping = new AddressMapping(qPort); private static final MultilingualStringMapping portInfoMapping = new MultilingualStringMapping(qPort.infoFi, qPort.infoSv, qPort.infoEn); private static final MappingProjection<Port> portMapping = new MappingProjection<Port>(Port.class, qPort.all()) { @Override protected Port map(Tuple row) { Boolean entry = row.get(qPort.entry); if (entry == null) { return null; } Point location = row.get(qPort.location); boolean exit = row.get(qPort.exit); boolean pedestrian = row.get(qPort.pedestrian); boolean bicycle = row.get(qPort.bicycle); Port port = new Port(location, entry, exit, pedestrian, bicycle); port.address = addressMapping.map(row); port.info = portInfoMapping.map(row); return port; } }; private static final MappingProjection<Pricing> pricingMapping = new MappingProjection<Pricing>(Pricing.class, qPricing.all()) { @Override protected Pricing map(Tuple row) { CapacityType capacityType = row.get(qPricing.capacityType); if (capacityType == null) { return null; } Pricing pricing = new Pricing(); pricing.capacityType = capacityType; pricing.usage = row.get(qPricing.usage); pricing.maxCapacity = row.get(qPricing.maxCapacity); pricing.dayType = row.get(qPricing.dayType); pricing.time = new TimeDuration(row.get(qPricing.fromTime), row.get(qPricing.untilTime)); pricing.price = pricingPriceMapping.map(row); return pricing; } }; private static final MappingProjection<UnavailableCapacity> unavailableCapacityMapping = new MappingProjection<UnavailableCapacity>(UnavailableCapacity.class, qPricing.capacityType, qPricing.usage, qUnavailableCapacity.capacity) { @Override protected UnavailableCapacity map(Tuple row) { CapacityType capacityType = row.get(qPricing.capacityType); if (capacityType == null) { return null; } UnavailableCapacity unavailableCapacity = new UnavailableCapacity(); unavailableCapacity.capacityType = capacityType; unavailableCapacity.usage = row.get(qPricing.usage); Integer capacity = row.get(qUnavailableCapacity.capacity); unavailableCapacity.capacity = (capacity != null ? capacity : 0); return unavailableCapacity; } }; private static final ResultTransformer<Map<Long, Set<String>>> aliasesByFacilityIdMapping = groupBy(qAlias.facilityId).as(set(qAlias.alias)); private static final ResultTransformer<Map<Long, List<Port>>> portsByFacilityIdMapping = groupBy(qPort.facilityId).as(list(portMapping)); private static final MultilingualStringMapping paymentInfoDetailMapping = new MultilingualStringMapping(qFacility.paymentInfoDetailFi, qFacility.paymentInfoDetailSv, qFacility.paymentInfoDetailEn); private static final MultilingualUrlMapping paymentInfoUrlMapping = new MultilingualUrlMapping(qFacility.paymentInfoUrlFi, qFacility.paymentInfoUrlSv, qFacility.paymentInfoUrlEn); private static final MultilingualStringMapping nameMapping = new MultilingualStringMapping(qFacility.nameFi, qFacility.nameSv, qFacility.nameEn); private static final MappingProjection<FacilityInfo> facilityInfoMapping = new MappingProjection<FacilityInfo>(FacilityInfo.class, qFacility.all()) { @Override protected FacilityInfo map(Tuple row) { return mapFacility(row, new FacilityInfo()); } }; private static <T extends FacilityInfo> T mapFacility(Tuple row, T facility) { Long id = row.get(qFacility.id); if (id == null) { return null; } facility.id = id; facility.location = row.get(qFacility.location); facility.name = nameMapping.map(row); facility.operatorId = row.get(qFacility.operatorId); facility.status = row.get(qFacility.status); facility.statusDescription = statusDescriptionMapping.map(row); facility.pricingMethod = row.get(qFacility.pricingMethod); if (row.get(qFacility.usageParkAndRide)) { facility.usages.add(PARK_AND_RIDE); } if (row.get(qFacility.usageHsl)) { facility.usages.add(HSL_TRAVEL_CARD); } if (row.get(qFacility.usageCommercial)) { facility.usages.add(COMMERCIAL); } mapCapacity(facility.builtCapacity, CAR, row.get(qFacility.capacityCar)); mapCapacity(facility.builtCapacity, DISABLED, row.get(qFacility.capacityDisabled)); mapCapacity(facility.builtCapacity, ELECTRIC_CAR, row.get(qFacility.capacityElectricCar)); mapCapacity(facility.builtCapacity, MOTORCYCLE, row.get(qFacility.capacityMotorcycle)); mapCapacity(facility.builtCapacity, BICYCLE, row.get(qFacility.capacityBicycle)); mapCapacity(facility.builtCapacity, BICYCLE_SECURE_SPACE, row.get(qFacility.capacityBicycleSecureSpace)); return facility; } private static final MappingProjection<Facility> facilityMapping = new MappingProjection<Facility>(Facility.class, qFacility.all()) { @Override protected Facility map(Tuple row) { Facility facility = mapFacility(row, new Facility()); facility.contacts = new FacilityContacts( row.get(qFacility.emergencyContactId), row.get(qFacility.operatorContactId), row.get(qFacility.serviceContactId) ); facility.paymentInfo.detail = paymentInfoDetailMapping.map(row); facility.paymentInfo.url = paymentInfoUrlMapping.map(row); facility.openingHours.info = openingHoursInfoMapping.map(row); facility.openingHours.url = openingHoursUrlMapping.map(row); return facility; } }; private static void mapCapacity(Map<CapacityType, Integer> capacities, CapacityType type, Integer capacity) { if (capacity != null && capacity > 0) { capacities.put(type, capacity); } } public static final String FACILITY_ID_SEQ = "facility_id_seq"; private static final SimpleExpression<Long> nextFacilityId = SQLExpressions.nextval(FACILITY_ID_SEQ); private final PostgreSQLQueryFactory queryFactory; private final FacilityHistoryRepository facilityHistoryRepository; public FacilityDao(PostgreSQLQueryFactory queryFactory, FacilityHistoryRepository facilityHistoryRepository) { this.queryFactory = queryFactory; this.facilityHistoryRepository = facilityHistoryRepository; } @TransactionalWrite @Override public long insertFacility(Facility facility) { return insertFacility(facility, queryFactory.query().select(nextFacilityId).fetchOne()); } @TransactionalWrite public long insertFacility(Facility facility, long facilityId) { checkNotNull(facility, "facility"); facility.normalize(); SQLInsertClause insert = insertFacility(); insert.set(qFacility.id, facilityId); populate(facility, insert); insert.execute(); insertAliases(facilityId, facility.aliases); insertPorts(facilityId, facility.ports); updateServices(facilityId, facility.services); updatePaymentMethods(facilityId, facility.paymentInfo.paymentMethods); insertPricing(facilityId, facility.pricingMethod.getPricing(facility)); insertUnavailableCapacity(facilityId, facility.unavailableCapacities); // History updated final DateTime currentDate = DateTime.now(); facilityHistoryRepository.updateStatusHistory(currentDate, facilityId, facility.status, facility.statusDescription); facilityHistoryRepository.updateCapacityHistory(currentDate, facilityId, facility.builtCapacity, facility.unavailableCapacities); return facilityId; } @TransactionalWrite @Override public void updateFacility(long facilityId, Facility facility) { updateFacility(facilityId, facility, getFacility(facility.id, true)); } @TransactionalWrite @Override public void updateFacility(long facilityId, Facility newFacility, Facility oldFacility) { checkNotNull(newFacility, "facility"); newFacility.normalize(); SQLUpdateClause update = updateFacility().where(qFacility.id.eq(facilityId)); populate(newFacility, update); if (update.execute() != 1) { throw new FacilityNotFoundException(facilityId); } updateAliases(facilityId, newFacility.aliases, oldFacility.aliases); updatePorts(facilityId, newFacility.ports, oldFacility.ports); if (!Objects.equals(newFacility.services, oldFacility.services)) { updateServices(facilityId, newFacility.services); } if (!Objects.equals(newFacility.paymentInfo.paymentMethods, oldFacility.paymentInfo.paymentMethods)) { updatePaymentMethods(facilityId, newFacility.paymentInfo.paymentMethods); } if (!Objects.equals(newFacility.pricing, oldFacility.pricing)) { deletePricing(facilityId); insertPricing(facilityId, newFacility.pricing); } if (!Objects.equals(newFacility.unavailableCapacities, oldFacility.unavailableCapacities)) { deleteUnavailableCapacity(facilityId); insertUnavailableCapacity(facilityId, newFacility.unavailableCapacities); } final DateTime now = DateTime.now(); if (!(Objects.equals(newFacility.status, oldFacility.status) && Objects.equals(newFacility.statusDescription, oldFacility.statusDescription))) { facilityHistoryRepository.updateStatusHistory(now, facilityId, newFacility.status, newFacility.statusDescription); } if (!(Objects.equals(newFacility.builtCapacity, oldFacility.builtCapacity)) || !(Objects.equals(newFacility.unavailableCapacities, oldFacility.unavailableCapacities))) { facilityHistoryRepository.updateCapacityHistory(now, facilityId, newFacility.builtCapacity, newFacility.unavailableCapacities); } } @TransactionalRead @Override public Facility getFacility(long facilityId) { return getFacility(facilityId, false); } @TransactionalRead @Override public FacilityInfo getFacilityInfo(long facilityId) { FacilityInfo facility = fromFacility().select(facilityInfoMapping).where(qFacility.id.eq(facilityId)).fetchOne(); if (facility == null) { throw new FacilityNotFoundException(facilityId); } return facility; } @TransactionalWrite @Override public Facility getFacilityForUpdate(long facilityId) { return getFacility(facilityId, true); } private Facility getFacility(long facilityId, boolean forUpdate) { PostgreSQLQuery<Facility> qry = fromFacility().select(facilityMapping).where(qFacility.id.eq(facilityId)); if (forUpdate) { qry.forUpdate(); } Facility facility = qry.fetchOne(); if (facility == null) { throw new FacilityNotFoundException(facilityId); } ImmutableMap<Long, Facility> facilityMap = ImmutableMap.of(facilityId, facility); fetchAliases(facilityMap); fetchPorts(facilityMap); fetchServices(facilityMap); fetchPaymentMethods(facilityMap); fetchPricing(facilityMap); fetchUnavailableCapacity(facilityMap); facility.initialize(); return facility; } @TransactionalRead @Override public SearchResults<FacilityInfo> findFacilities(PageableFacilitySearch search) { final PostgreSQLQuery<FacilityInfo> qry = fromFacility().select(facilityInfoMapping); if (search.limit >= 0) { qry.limit(search.limit + 1); // find one extra for hasMore } if (search.offset > 0) { qry.offset(search.offset); } buildWhere(search, qry); orderBy(search.sort, qry); Map<Long, FacilityInfo> facilities = qry.transform(groupBy(qFacility.id).as(facilityInfoMapping)); return SearchResults.of(facilities.values(), search.limit); } @TransactionalRead @Override public FacilitySummary summarizeFacilities(FacilitySearch search) { PostgreSQLQuery<?> qry = fromFacility(); buildWhere(search, qry); Tuple result = qry.select( qFacility.id.count(), qFacility.capacityCar.sum(), qFacility.capacityDisabled.sum(), qFacility.capacityElectricCar.sum(), qFacility.capacityMotorcycle.sum(), qFacility.capacityBicycle.sum(), qFacility.capacityBicycleSecureSpace.sum()).fetchOne(); // FIXME: what if (result == null) ?!? assert result != null; Map<CapacityType, Integer> capacities = Maps.newHashMap(); mapCapacity(capacities, CAR, result.get(qFacility.capacityCar.sum())); mapCapacity(capacities, DISABLED, result.get(qFacility.capacityDisabled.sum())); mapCapacity(capacities, ELECTRIC_CAR, result.get(qFacility.capacityElectricCar.sum())); mapCapacity(capacities, MOTORCYCLE, result.get(qFacility.capacityMotorcycle.sum())); mapCapacity(capacities, BICYCLE, result.get(qFacility.capacityBicycle.sum())); mapCapacity(capacities, BICYCLE_SECURE_SPACE, result.get(qFacility.capacityBicycleSecureSpace.sum())); return new FacilitySummary(result.get(qFacility.id.count()), capacities); } private void updateAliases(long facilityId, Set<String> newAliases, Set<String> oldAliases) { Set<String> toBeRemoved = new HashSet<>(oldAliases); Set<String> addedAliases = Sets.newHashSet(); if (newAliases != null) { for (String newAlias : newAliases) { if (!toBeRemoved.remove(newAlias)) { addedAliases.add(newAlias); } } } insertAliases(facilityId, addedAliases); deleteAliases(facilityId, toBeRemoved); } private void deleteAliases(long facilityId, Set<String> aliases) { if (!aliases.isEmpty()) { queryFactory.delete(qAlias) .where(qAlias.facilityId.eq(facilityId), qAlias.alias.in(aliases)) .execute(); } } private void updatePorts(long facilityId, List<Port> newPorts, List<Port> oldPorts) { newPorts = Optional.ofNullable(newPorts).orElse(new ArrayList<>()); oldPorts = Optional.ofNullable(oldPorts).orElse(new ArrayList<>()); Map<Integer, Port> addedPorts = new HashMap<>(); Map<Integer, Port> updatedPorts = new HashMap<>(); for (int i = 0; i < newPorts.size(); i++) { Port newPort = newPorts.get(i); Port oldPort = i < oldPorts.size() ? oldPorts.get(i) : null; if (oldPort == null) { addedPorts.put(i, newPort); } else if (!newPort.equals(oldPort)) { updatedPorts.put(i, newPort); } } insertPorts(facilityId, addedPorts); updatePorts(facilityId, updatedPorts); if (oldPorts.size() > newPorts.size()) { deletePorts(facilityId, newPorts.size()); } } private void updatePorts(long facilityId, Map<Integer, Port> updatedPorts) { if (updatedPorts != null && !updatedPorts.isEmpty()) { SQLUpdateClause update = queryFactory.update(qPort); for (Entry<Integer, Port> entry : updatedPorts.entrySet()) { Integer portIndex = entry.getKey(); populate(facilityId, portIndex, entry.getValue(), update); update.where(qPort.facilityId.eq(facilityId), qPort.portIndex.eq(portIndex)); update.addBatch(); } update.execute(); } } private void insertPorts(long facilityId, List<Port> ports) { if (ports != null && !ports.isEmpty()) { Map<Integer, Port> addedPorts = new HashMap<>(); for (int i = 0; i < ports.size(); i++) { addedPorts.put(i, ports.get(i)); } insertPorts(facilityId, addedPorts); } } private void insertPorts(long facilityId, Map<Integer, Port> addedPorts) { if (addedPorts != null && !addedPorts.isEmpty()) { SQLInsertClause insert = queryFactory.insert(qPort); for (Entry<Integer, Port> entry : addedPorts.entrySet()) { populate(facilityId, entry.getKey(), entry.getValue(), insert); insert.addBatch(); } insert.execute(); } } private void populate(long facilityId, Integer index, Port port, StoreClause store) { store.set(qPort.facilityId, facilityId) .set(qPort.portIndex, index) .set(qPort.location, port.location) .set(qPort.entry, port.entry) .set(qPort.exit, port.exit) .set(qPort.bicycle, port.bicycle) .set(qPort.pedestrian, port.pedestrian); addressMapping.populate(port.address, store); portInfoMapping.populate(port.info, store); } private void deletePorts(long facilityId, int fromIndex) { queryFactory.delete(qPort).where(qPort.facilityId.eq(facilityId), qPort.portIndex.goe(fromIndex)).execute(); } private void insertPricing(long facilityId, List<Pricing> pricing) { if (pricing != null && !pricing.isEmpty()) { SQLInsertClause insert = queryFactory.insert(qPricing); for (Pricing price : pricing) { insert.set(qPricing.facilityId, facilityId) .set(qPricing.capacityType, price.capacityType) .set(qPricing.usage, price.usage) .set(qPricing.maxCapacity, price.maxCapacity) .set(qPricing.dayType, price.dayType) .set(qPricing.fromTime, price.time.from) .set(qPricing.untilTime, price.time.until); pricingPriceMapping.populate(price.price, insert); insert.addBatch(); } insert.execute(); } } private void deletePricing(long facilityId) { queryFactory.delete(qPricing).where(qPricing.facilityId.eq(facilityId)).execute(); } private void insertUnavailableCapacity(long facilityId, List<UnavailableCapacity> unavailableCapacities) { if (unavailableCapacities != null && !unavailableCapacities.isEmpty()) { SQLInsertClause insert = queryFactory.insert(qUnavailableCapacity); for (UnavailableCapacity unavailableCapacity : unavailableCapacities) { insert.set(qUnavailableCapacity.facilityId, facilityId) .set(qUnavailableCapacity.capacityType, unavailableCapacity.capacityType) .set(qUnavailableCapacity.usage, unavailableCapacity.usage) .set(qUnavailableCapacity.capacity, unavailableCapacity.capacity); insert.addBatch(); } insert.execute(); } } private void deleteUnavailableCapacity(long facilityId) { queryFactory.delete(qUnavailableCapacity).where(qUnavailableCapacity.facilityId.eq(facilityId)).execute(); } private void orderBy(Sort sort, PostgreSQLQuery qry) { sort = firstNonNull(sort, DEFAULT_SORT); ComparableExpression<String> sortField; switch (firstNonNull(sort.getBy(), DEFAULT_SORT.getBy())) { case "name.fi": sortField = qFacility.nameFi.lower(); break; case "name.sv": sortField = qFacility.nameSv.lower(); break; case "name.en": sortField = qFacility.nameEn.lower(); break; default: throw invalidSortBy(); } if (DESC.equals(sort.getDir())) { qry.orderBy(sortField.desc(), qFacility.id.desc()); } else { qry.orderBy(sortField.asc(), qFacility.id.asc()); } } private ValidationException invalidSortBy() { return new ValidationException(new Violation("SortBy", "sort.by", "Expected one of 'name.fi', 'name.sv' or 'name.en'")); } private void buildWhere(FacilitySearch search, PostgreSQLQuery qry) { if (!isEmpty(search.getStatuses())) { qry.where(qFacility.status.in(search.getStatuses())); } if (!isEmpty(search.getIds())) { qry.where(qFacility.id.in(search.getIds())); } if (search.getOperatorId() != null) { qry.where(qFacility.operatorId.eq(search.getOperatorId())); } if (search.getGeometry() != null) { if (search.getMaxDistance() != null && search.getMaxDistance() > 0) { qry.where(dwithin(qFacility.location, ConstantImpl.create(search.getGeometry()), search.getMaxDistance())); } else { qry.where(qFacility.location.intersects(search.getGeometry())); } } } private void insertAliases(long facilityId, Collection<String> aliases) { if (aliases != null && !aliases.isEmpty()) { SQLInsertClause insertBatch = queryFactory.insert(qAlias); for (String alias : aliases) { insertBatch.set(qAlias.facilityId, facilityId); insertBatch.set(qAlias.alias, alias); insertBatch.addBatch(); } insertBatch.execute(); } } private void updateServices(long facilityId, Set<Service> services) { queryFactory.delete(qService).where(qService.facilityId.eq(facilityId)).execute(); if (services != null && !services.isEmpty()) { SQLInsertClause insert = queryFactory.insert(qService); for (Service service : services) { insert.set(qService.facilityId, facilityId).set(qService.service, service).addBatch(); } insert.execute(); } } private void updatePaymentMethods(long facilityId, Set<PaymentMethod> paymentMethods) { queryFactory.delete(qPaymentMethod).where(qPaymentMethod.facilityId.eq(facilityId)).execute(); if (paymentMethods != null && !paymentMethods.isEmpty()) { SQLInsertClause insert = queryFactory.insert(qPaymentMethod); for (PaymentMethod paymentMethod : paymentMethods) { insert.set(qPaymentMethod.facilityId, facilityId).set(qPaymentMethod.paymentMethod, paymentMethod).addBatch(); } insert.execute(); } } private void fetchPorts(Map<Long, Facility> facilitiesById) { if (!facilitiesById.isEmpty()) { Map<Long, List<Port>> ports = findPorts(facilitiesById.keySet()); for (Entry<Long, List<Port>> entry : ports.entrySet()) { facilitiesById.get(entry.getKey()).ports = entry.getValue(); } } } private void fetchAliases(Map<Long, Facility> facilitiesById) { if (!facilitiesById.isEmpty()) { Map<Long, Set<String>> aliasesByFacilityId = findAliases(facilitiesById.keySet()); for (Entry<Long, Set<String>> entry : aliasesByFacilityId.entrySet()) { facilitiesById.get(entry.getKey()).aliases = new TreeSet<>(entry.getValue()); } } } private void fetchServices(Map<Long, Facility> facilitiesById) { if (!facilitiesById.isEmpty()) { Map<Long, NullSafeSortedSet<Service>> services = findServices(facilitiesById.keySet()); for (Entry<Long, NullSafeSortedSet<Service>> entry : services.entrySet()) { facilitiesById.get(entry.getKey()).services = entry.getValue(); } } } private void fetchPaymentMethods(Map<Long, Facility> facilitiesById) { if (!facilitiesById.isEmpty()) { Map<Long, NullSafeSortedSet<PaymentMethod>> paymentMethods = findPaymentMethods(facilitiesById.keySet()); for (Entry<Long, NullSafeSortedSet<PaymentMethod>> entry : paymentMethods.entrySet()) { facilitiesById.get(entry.getKey()).paymentInfo.paymentMethods = entry.getValue(); } } } private void fetchPricing(Map<Long, Facility> facilitiesById) { if (!facilitiesById.isEmpty()) { Map<Long, List<Pricing>> pricing = findPricing(facilitiesById.keySet()); for (Entry<Long, List<Pricing>> entry : pricing.entrySet()) { Facility facility = facilitiesById.get(entry.getKey()); facility.pricing = entry.getValue(); } } } private void fetchUnavailableCapacity(Map<Long, Facility> facilitiesById) { if (!facilitiesById.isEmpty()) { Map<Long, List<UnavailableCapacity>> pricing = findUnavailableCapacity(facilitiesById.keySet()); for (Entry<Long, List<UnavailableCapacity>> entry : pricing.entrySet()) { Facility facility = facilitiesById.get(entry.getKey()); facility.unavailableCapacities = entry.getValue(); } } } private Map<Long, List<Pricing>> findPricing(Set<Long> facilityIds) { return queryFactory.from(qPricing) .where(qPricing.facilityId.in(facilityIds)) .transform(groupBy(qPricing.facilityId).as(list(pricingMapping))); } private Map<Long, List<UnavailableCapacity>> findUnavailableCapacity(Set<Long> facilityIds) { return queryFactory.from(qPricing) .leftJoin(qUnavailableCapacity).on( qPricing.facilityId.eq(qUnavailableCapacity.facilityId), qPricing.capacityType.eq(qUnavailableCapacity.capacityType), qPricing.usage.eq(qUnavailableCapacity.usage)) .distinct() .where(qPricing.facilityId.in(facilityIds)) .transform(groupBy(qPricing.facilityId).as(list(unavailableCapacityMapping))); } private Map<Long, NullSafeSortedSet<Service>> findServices(Set<Long> facilityIds) { return queryFactory.from(qService) .where(qService.facilityId.in(facilityIds)) .transform(groupBy(qService.facilityId).as(sortedSet(qService.service))); } private Map<Long, NullSafeSortedSet<PaymentMethod>> findPaymentMethods(Set<Long> facilityIds) { return queryFactory.from(qPaymentMethod) .where(qPaymentMethod.facilityId.in(facilityIds)) .transform(groupBy(qPaymentMethod.facilityId).as(sortedSet(qPaymentMethod.paymentMethod))); } private Map<Long, Set<String>> findAliases(Set<Long> facilityIds) { return queryFactory.from(qAlias) .where(qAlias.facilityId.in(facilityIds)) .transform(aliasesByFacilityIdMapping); } private Map<Long, List<Port>> findPorts(Set<Long> facilitiesById) { return queryFactory.from(qPort) .where(qPort.facilityId.in(facilitiesById)) .transform(portsByFacilityIdMapping); } private void populate(Facility facility, StoreClause store) { nameMapping.populate(facility.name, store); store.set(qFacility.location, facility.location); store.set(qFacility.operatorId, facility.operatorId); store.set(qFacility.status, facility.status); store.set(qFacility.pricingMethod, facility.pricingMethod); statusDescriptionMapping.populate(facility.statusDescription, store); FacilityContacts contacts = facility.contacts != null ? facility.contacts : new FacilityContacts(); store.set(qFacility.emergencyContactId, contacts.emergency); store.set(qFacility.operatorContactId, contacts.operator); store.set(qFacility.serviceContactId, contacts.service); openingHoursInfoMapping.populate(facility.openingHours.info, store); openingHoursUrlMapping.populate(facility.openingHours.url, store); Set<Usage> usages = facility.analyzeUsages(); store.set(qFacility.usageParkAndRide, usages.contains(PARK_AND_RIDE)); store.set(qFacility.usageHsl, usages.contains(HSL_TRAVEL_CARD)); store.set(qFacility.usageCommercial, usages.contains(COMMERCIAL)); Map<CapacityType, Integer> builtCapacity = facility.builtCapacity != null ? facility.builtCapacity : ImmutableMap.of(); populateCapacity(qFacility.capacityCar, builtCapacity.get(CAR), store); populateCapacity(qFacility.capacityDisabled, builtCapacity.get(DISABLED), store); populateCapacity(qFacility.capacityElectricCar, builtCapacity.get(ELECTRIC_CAR), store); populateCapacity(qFacility.capacityMotorcycle, builtCapacity.get(MOTORCYCLE), store); populateCapacity(qFacility.capacityBicycle, builtCapacity.get(BICYCLE), store); populateCapacity(qFacility.capacityBicycleSecureSpace, builtCapacity.get(BICYCLE_SECURE_SPACE), store); FacilityPaymentInfo paymentInfo = facility.paymentInfo; paymentInfoDetailMapping.populate(paymentInfo.detail, store); paymentInfoUrlMapping.populate(paymentInfo.url, store); } private void populateCapacity(NumberPath<Integer> path, Integer value, StoreClause store) { if (value == null || value < 1) { store.setNull(path); } else { store.set(path, value); } } private SQLInsertClause insertFacility() { return queryFactory.insert(qFacility); } private SQLUpdateClause updateFacility() { return queryFactory.update(qFacility); } private PostgreSQLQuery<?> fromFacility() { return queryFactory.from(qFacility); } }