diff --git a/src/main/java/org/entur/lamassu/controller/GBFSFeedController.java b/src/main/java/org/entur/lamassu/controller/GBFSFeedController.java index 4730c863..7bf6d489 100644 --- a/src/main/java/org/entur/lamassu/controller/GBFSFeedController.java +++ b/src/main/java/org/entur/lamassu/controller/GBFSFeedController.java @@ -8,6 +8,7 @@ import org.entur.lamassu.model.discovery.System; import org.entur.lamassu.model.discovery.SystemDiscovery; import org.entur.lamassu.model.provider.FeedProvider; +import org.entur.lamassu.service.FeedAvailabilityService; import org.entur.lamassu.service.FeedProviderService; import org.entur.lamassu.service.SystemDiscoveryService; import org.springframework.beans.factory.annotation.Autowired; @@ -28,6 +29,8 @@ public class GBFSFeedController { private final GBFSFeedCache feedCache; private final FeedProviderService feedProviderService; + private final FeedAvailabilityService feedAvailabilityService; + @Value("${org.entur.lamassu.baseUrl}") private String baseUrl; @@ -35,10 +38,11 @@ public class GBFSFeedController { private String internalLoadBalancer; @Autowired - public GBFSFeedController(SystemDiscoveryService systemDiscoveryService, GBFSFeedCache feedCache, FeedProviderService feedProviderService) { + public GBFSFeedController(SystemDiscoveryService systemDiscoveryService, GBFSFeedCache feedCache, FeedProviderService feedProviderService, FeedAvailabilityService feedAvailabilityService) { this.systemDiscoveryService = systemDiscoveryService; this.feedCache = feedCache; this.feedProviderService = feedProviderService; + this.feedAvailabilityService = feedAvailabilityService; } @GetMapping("/gbfs") @@ -72,8 +76,9 @@ public Object getGbfsFeedForProvider(@PathVariable String systemId, @PathVariabl try { var feedName = GBFSFeedName.fromValue(feed); var feedProvider = feedProviderService.getFeedProviderBySystemId(systemId); + var availableFiles = feedAvailabilityService.getAvailableFiles(systemId); - if (feedProvider == null) { + if (feedProvider == null || availableFiles == null || !availableFiles.contains(feedName) && !feedName.equals(GBFSFeedName.GBFS)) { throw new NoSuchElementException(); } diff --git a/src/main/java/org/entur/lamassu/leader/FeedUpdater.java b/src/main/java/org/entur/lamassu/leader/FeedUpdater.java index 1d7235be..6a5e7b64 100644 --- a/src/main/java/org/entur/lamassu/leader/FeedUpdater.java +++ b/src/main/java/org/entur/lamassu/leader/FeedUpdater.java @@ -21,6 +21,7 @@ import org.entur.gbfs.GbfsDelivery; import org.entur.gbfs.GbfsSubscriptionManager; import org.entur.gbfs.GbfsSubscriptionOptions; +import org.entur.gbfs.v2_3.gbfs.GBFSFeed; import org.entur.gbfs.validation.model.ValidationResult; import org.entur.lamassu.config.feedprovider.FeedProviderConfig; import org.entur.lamassu.leader.entityupdater.EntityCachesUpdater; @@ -28,6 +29,7 @@ import org.entur.lamassu.mapper.feedmapper.GbfsDeliveryMapper; import org.entur.lamassu.metrics.MetricsService; import org.entur.lamassu.model.provider.FeedProvider; +import org.entur.lamassu.service.FeedAvailabilityService; import org.redisson.api.RBucket; import org.redisson.api.RMapCache; import org.slf4j.Logger; @@ -41,6 +43,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; @Component @Profile("leader") @@ -65,6 +68,8 @@ public class FeedUpdater { private MetricsService metricsService; + private FeedAvailabilityService feedAvailabilityService; + @Autowired public FeedUpdater( FeedProviderConfig feedProviderConfig, @@ -73,7 +78,8 @@ public FeedUpdater( EntityCachesUpdater entityCachesUpdater, RMapCache validationResultCache, RBucket cacheReady, - MetricsService metricsService + MetricsService metricsService, + FeedAvailabilityService feedAvailabilityService ) { this.feedProviderConfig = feedProviderConfig; this.gbfsDeliveryMapper = gbfsDeliveryMapper; @@ -82,6 +88,7 @@ public FeedUpdater( this.validationResultCache = validationResultCache; this.cacheReady = cacheReady; this.metricsService = metricsService; + this.feedAvailabilityService = feedAvailabilityService; } public void start() { @@ -138,7 +145,19 @@ private void receiveUpdate(FeedProvider feedProvider, GbfsDelivery delivery) { var mappedDelivery = gbfsDeliveryMapper.mapGbfsDelivery(delivery, feedProvider); var oldDelivery = feedCachesUpdater.updateFeedCaches(feedProvider, mappedDelivery); - entityCachesUpdater.updateEntityCaches(feedProvider, mappedDelivery, oldDelivery); + + try { + entityCachesUpdater.updateEntityCaches(feedProvider, mappedDelivery, oldDelivery); + } catch (ClassCastException e) { + logger.warn("This should not occur in production", e); + } + + + feedAvailabilityService.setAvailableFiles( + feedProvider.getSystemId(), + mappedDelivery.getDiscovery().getFeedsData().get(feedProvider.getLanguage()).getFeeds().stream().map(GBFSFeed::getName).collect(Collectors.toList()) + ); + cacheReady.set(true); } } diff --git a/src/main/java/org/entur/lamassu/mapper/feedmapper/DiscoveryFeedPostProcessor.java b/src/main/java/org/entur/lamassu/mapper/feedmapper/DiscoveryFeedPostProcessor.java new file mode 100644 index 00000000..dece9347 --- /dev/null +++ b/src/main/java/org/entur/lamassu/mapper/feedmapper/DiscoveryFeedPostProcessor.java @@ -0,0 +1,62 @@ +/* + * + * + * * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by + * * the European Commission - subsequent versions of the EUPL (the "Licence"); + * * You may not use this work except in compliance with the Licence. + * * You may obtain a copy of the Licence at: + * * + * * https://joinup.ec.europa.eu/software/page/eupl + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the Licence is distributed on an "AS IS" basis, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the Licence for the specific language governing permissions and + * * limitations under the Licence. + * + */ + +package org.entur.lamassu.mapper.feedmapper; + +import org.entur.gbfs.GbfsDelivery; +import org.entur.gbfs.v2_3.gbfs.GBFS; + +import java.util.stream.Collectors; + +public class DiscoveryFeedPostProcessor { + public static void removeUnavailableFiles(GBFS discovery, GbfsDelivery mapped) { + discovery.getFeedsData().keySet().forEach(language -> { + var feeds = discovery.getFeedsData().get(language).getFeeds().stream().filter(feed -> { + switch (feed.getName()) { + case SystemInformation: + return mapped.getSystemInformation() != null; + case VehicleTypes: + return mapped.getVehicleTypes() != null; + case FreeBikeStatus: + return mapped.getFreeBikeStatus() != null; + case StationInformation: + return mapped.getStationInformation() != null; + case StationStatus: + return mapped.getStationStatus() != null; + case SystemPricingPlans: + return mapped.getSystemPricingPlans() != null; + case SystemAlerts: + return mapped.getSystemAlerts() != null; + case SystemHours: + return mapped.getSystemHours() != null; + case SystemCalendar: + return mapped.getSystemCalendar() != null; + case SystemRegions: + return mapped.getSystemRegions() != null; + case GeofencingZones: + return mapped.getGeofencingZones() != null; + default: + return false; + } + }).collect(Collectors.toList()); + discovery.getFeedsData().get(language).setFeeds(feeds); + }); + + + } +} diff --git a/src/main/java/org/entur/lamassu/mapper/feedmapper/GbfsDeliveryMapper.java b/src/main/java/org/entur/lamassu/mapper/feedmapper/GbfsDeliveryMapper.java index 037cd77c..491831f2 100644 --- a/src/main/java/org/entur/lamassu/mapper/feedmapper/GbfsDeliveryMapper.java +++ b/src/main/java/org/entur/lamassu/mapper/feedmapper/GbfsDeliveryMapper.java @@ -82,7 +82,6 @@ public GbfsDeliveryMapper( // mapping of the versions file, if it exists, is intentionally skipped. public GbfsDelivery mapGbfsDelivery(GbfsDelivery delivery, FeedProvider feedProvider) { var mapped = new GbfsDelivery(); - mapped.setDiscovery(discoveryFeedMapper.map(delivery.getDiscovery(), feedProvider)); mapped.setSystemInformation(systemInformationFeedMapper.map(delivery.getSystemInformation(), feedProvider)); mapped.setSystemAlerts(systemAlertsFeedMapper.map(delivery.getSystemAlerts(), feedProvider)); mapped.setSystemCalendar(systemCalendarFeedMapper.map(delivery.getSystemCalendar(), feedProvider)); @@ -99,6 +98,11 @@ public GbfsDelivery mapGbfsDelivery(GbfsDelivery delivery, FeedProvider feedProv stationStatus -> VehicleTypeCapacityProducer.addToStations(stationStatus, mapped.getVehicleTypes())) ); mapped.setFreeBikeStatus(freeBikeStatusFeedMapper.map(delivery.getFreeBikeStatus(), feedProvider)); + mapped.setDiscovery( + discoveryFeedMapper.map( + delivery.getDiscovery(), + feedProvider, + discovery -> DiscoveryFeedPostProcessor.removeUnavailableFiles(discovery, mapped))); return mapped; } } diff --git a/src/main/java/org/entur/lamassu/service/FeedAvailabilityService.java b/src/main/java/org/entur/lamassu/service/FeedAvailabilityService.java new file mode 100644 index 00000000..3474e05a --- /dev/null +++ b/src/main/java/org/entur/lamassu/service/FeedAvailabilityService.java @@ -0,0 +1,30 @@ +/* + * + * + * * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by + * * the European Commission - subsequent versions of the EUPL (the "Licence"); + * * You may not use this work except in compliance with the Licence. + * * You may obtain a copy of the Licence at: + * * + * * https://joinup.ec.europa.eu/software/page/eupl + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the Licence is distributed on an "AS IS" basis, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the Licence for the specific language governing permissions and + * * limitations under the Licence. + * + */ + +package org.entur.lamassu.service; + +import org.entur.gbfs.v2_3.gbfs.GBFSFeedName; + +import java.util.List; +import java.util.Map; + +public interface FeedAvailabilityService { + Map> getAvailableFeeds(); + List getAvailableFiles(String systemId); + void setAvailableFiles(String systemId, List files); +} diff --git a/src/main/java/org/entur/lamassu/service/impl/MockFeedAvailabilityService.java b/src/main/java/org/entur/lamassu/service/impl/MockFeedAvailabilityService.java new file mode 100644 index 00000000..933fb993 --- /dev/null +++ b/src/main/java/org/entur/lamassu/service/impl/MockFeedAvailabilityService.java @@ -0,0 +1,59 @@ +/* + * + * + * * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by + * * the European Commission - subsequent versions of the EUPL (the "Licence"); + * * You may not use this work except in compliance with the Licence. + * * You may obtain a copy of the Licence at: + * * + * * https://joinup.ec.europa.eu/software/page/eupl + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the Licence is distributed on an "AS IS" basis, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the Licence for the specific language governing permissions and + * * limitations under the Licence. + * + */ + +package org.entur.lamassu.service.impl; + +import org.entur.gbfs.v2_3.gbfs.GBFSFeedName; +import org.entur.lamassu.service.FeedAvailabilityService; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This mock service is only to demonstrate functionality. A service based on redis should be implemented + * before merging. + */ +@Component +public class MockFeedAvailabilityService implements FeedAvailabilityService { + private final ConcurrentHashMap> availableFilesPerSystem = new ConcurrentHashMap<>(); + + @Override + public Map> getAvailableFeeds() { + return availableFilesPerSystem; + } + + @Override + public List getAvailableFiles(String systemId) { + return availableFilesPerSystem.get(systemId); + } + + @Override + public void setAvailableFiles(String systemId, List files) { + var minimumRequired = files.containsAll(List.of(GBFSFeedName.SystemInformation, GBFSFeedName.VehicleTypes, GBFSFeedName.SystemPricingPlans)); + var freeFloating = files.contains(GBFSFeedName.FreeBikeStatus); + var stationBased = files.containsAll(List.of(GBFSFeedName.StationInformation, GBFSFeedName.StationStatus)); + + if (minimumRequired && (freeFloating || stationBased)) { + availableFilesPerSystem.put(systemId, files); + } else { + availableFilesPerSystem.remove(systemId); + } + } +} diff --git a/src/main/java/org/entur/lamassu/service/impl/SystemDiscoveryServiceImpl.java b/src/main/java/org/entur/lamassu/service/impl/SystemDiscoveryServiceImpl.java index 23a6e89b..981f20ba 100644 --- a/src/main/java/org/entur/lamassu/service/impl/SystemDiscoveryServiceImpl.java +++ b/src/main/java/org/entur/lamassu/service/impl/SystemDiscoveryServiceImpl.java @@ -2,6 +2,7 @@ import org.entur.lamassu.mapper.entitymapper.SystemDiscoveryMapper; import org.entur.lamassu.model.discovery.SystemDiscovery; +import org.entur.lamassu.service.FeedAvailabilityService; import org.entur.lamassu.service.FeedProviderService; import org.entur.lamassu.service.SystemDiscoveryService; import org.springframework.beans.factory.annotation.Autowired; @@ -11,19 +12,27 @@ @Component public class SystemDiscoveryServiceImpl implements SystemDiscoveryService { - private final SystemDiscovery systemDiscovery; + + private final FeedProviderService feedProviderService; + private final FeedAvailabilityService feedAvailabilityService; + private final SystemDiscoveryMapper systemDiscoveryMapper; @Autowired - public SystemDiscoveryServiceImpl(FeedProviderService feedProviderService, SystemDiscoveryMapper systemDiscoveryMapper) { - systemDiscovery = new SystemDiscovery(); - systemDiscovery.setSystems( - feedProviderService.getFeedProviders().stream() - .map(systemDiscoveryMapper::mapSystemDiscovery).collect(Collectors.toList()) - ); + public SystemDiscoveryServiceImpl(FeedProviderService feedProviderService, FeedAvailabilityService feedAvailabilityService, SystemDiscoveryMapper systemDiscoveryMapper) { + this.feedProviderService = feedProviderService; + this.feedAvailabilityService = feedAvailabilityService; + this.systemDiscoveryMapper = systemDiscoveryMapper; } @Override public SystemDiscovery getSystemDiscovery() { + var systemDiscovery = new SystemDiscovery(); + systemDiscovery.setSystems( + feedAvailabilityService.getAvailableFeeds().keySet().stream() + .map(feedProviderService::getFeedProviderBySystemId) + .map(systemDiscoveryMapper::mapSystemDiscovery) + .collect(Collectors.toList()) + ); return systemDiscovery; } }