Skip to content

Commit c36b6c6

Browse files
committed
feat: add mobility conditions
1 parent 8dba964 commit c36b6c6

20 files changed

+976
-0
lines changed

src/main/java/no/fdk/referencedata/Scheduler.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import no.fdk.referencedata.geonorge.administrativeenheter.EnhetService;
2323
import no.fdk.referencedata.iana.mediatype.MediaTypeService;
2424
import no.fdk.referencedata.los.LosService;
25+
import no.fdk.referencedata.mobility.conditions.MobilityConditionService;
2526
import no.fdk.referencedata.mobility.datastandard.MobilityDataStandardService;
2627
import no.fdk.referencedata.mobility.theme.MobilityThemeService;
2728
import no.fdk.referencedata.ssb.fylkeorganisasjoner.FylkeOrganisasjonService;
@@ -117,6 +118,9 @@ public class Scheduler {
117118
@Autowired
118119
private MobilityDataStandardService mobilityDataStandardService;
119120

121+
@Autowired
122+
private MobilityConditionService mobilityConditionService;
123+
120124
/**
121125
* Run every hour
122126
*/
@@ -317,6 +321,14 @@ public void updateMobilityThemes() {
317321
mobilityThemeService.harvestAndSave(false);
318322
}
319323

324+
/**
325+
* Run every day 04:55 (at night)
326+
*/
327+
@Scheduled(cron = "0 55 4 * * ?")
328+
public void updateMobilityCondition() {
329+
mobilityConditionService.harvestAndSave(false);
330+
}
331+
320332
/**
321333
* Run every day 05:00 (at night)
322334
*/
@@ -427,6 +439,10 @@ public void init() {
427439
mobilityThemeService.harvestAndSave(true);
428440
}
429441

442+
if(mobilityConditionService.firstTime()) {
443+
mobilityConditionService.harvestAndSave(true);
444+
}
445+
430446
if(mobilityDataStandardService.firstTime()) {
431447
mobilityDataStandardService.harvestAndSave(true);
432448
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package no.fdk.referencedata.graphql.query;
2+
3+
import no.fdk.referencedata.mobility.conditions.MobilityCondition;
4+
import no.fdk.referencedata.mobility.conditions.MobilityConditionRepository;
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.graphql.data.method.annotation.Argument;
7+
import org.springframework.graphql.data.method.annotation.QueryMapping;
8+
import org.springframework.stereotype.Controller;
9+
10+
import java.util.Comparator;
11+
import java.util.List;
12+
import java.util.stream.Collectors;
13+
import java.util.stream.StreamSupport;
14+
15+
@Controller
16+
public class MobilityConditionQuery {
17+
private final MobilityConditionRepository mobilityConditionRepository;
18+
19+
@Autowired
20+
public MobilityConditionQuery(MobilityConditionRepository mobilityConditionRepository) {
21+
this.mobilityConditionRepository = mobilityConditionRepository;
22+
}
23+
24+
@QueryMapping
25+
public List<MobilityCondition> mobilityConditions() {
26+
return StreamSupport.stream(mobilityConditionRepository.findAll().spliterator(), false)
27+
.sorted(Comparator.comparing(MobilityCondition::getUri))
28+
.collect(Collectors.toList());
29+
}
30+
31+
@QueryMapping
32+
public MobilityCondition mobilityConditionByCode(@Argument String code) {
33+
return mobilityConditionRepository.findByCode(code).orElse(null);
34+
}
35+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package no.fdk.referencedata.mobility.conditions;
2+
3+
import lombok.Builder;
4+
import lombok.Data;
5+
import org.springframework.data.annotation.Id;
6+
import org.springframework.data.mongodb.core.index.Indexed;
7+
import org.springframework.data.mongodb.core.mapping.Document;
8+
9+
import java.util.Map;
10+
11+
@Data
12+
@Builder
13+
@Document
14+
public class MobilityCondition {
15+
@Id
16+
String uri;
17+
@Indexed
18+
String code;
19+
Map<String, String> label;
20+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package no.fdk.referencedata.mobility.conditions;
2+
3+
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.apache.jena.riot.RDFFormat;
6+
import org.springframework.beans.factory.annotation.Autowired;
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.web.bind.annotation.*;
9+
10+
import java.util.Comparator;
11+
import java.util.stream.Collectors;
12+
import java.util.stream.StreamSupport;
13+
14+
@RestController
15+
@RequestMapping("/mobility/conditions-for-access-and-usage")
16+
@Slf4j
17+
public class MobilityConditionController {
18+
private final MobilityConditionRepository mobilityConditionRepository;
19+
private final MobilityConditionService mobilityConditionService;
20+
21+
@Autowired
22+
public MobilityConditionController(MobilityConditionRepository mobilityConditionRepository,
23+
MobilityConditionService mobilityConditionService) {
24+
this.mobilityConditionRepository = mobilityConditionRepository;
25+
this.mobilityConditionService = mobilityConditionService;
26+
}
27+
28+
@CrossOrigin
29+
@GetMapping
30+
public ResponseEntity<MobilityConditions> getMobilityConditions() {
31+
return ResponseEntity.ok(MobilityConditions.builder().mobilityConditions(
32+
StreamSupport.stream(mobilityConditionRepository.findAll().spliterator(), false)
33+
.sorted(Comparator.comparing(MobilityCondition::getUri))
34+
.collect(Collectors.toList())).build());
35+
}
36+
37+
@CrossOrigin
38+
@SecurityRequirement(name = "apiKey")
39+
@PostMapping
40+
public ResponseEntity<Void> updateMobilityConditions() {
41+
mobilityConditionService.harvestAndSave(true);
42+
return ResponseEntity.ok().build();
43+
}
44+
45+
@CrossOrigin
46+
@GetMapping(path = "/{code}")
47+
public ResponseEntity<MobilityCondition> getRoleType(@PathVariable("code") String code) {
48+
return ResponseEntity.of(mobilityConditionRepository.findByCode(code));
49+
}
50+
51+
@CrossOrigin
52+
@GetMapping(produces = "text/turtle")
53+
public ResponseEntity<String> getMobilityConditionsRDF() {
54+
return ResponseEntity.ok(mobilityConditionService.getRdf(RDFFormat.TURTLE));
55+
}
56+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package no.fdk.referencedata.mobility.conditions;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
import no.fdk.referencedata.i18n.Language;
5+
import no.fdk.referencedata.mobility.AbstractMobilityHarvester;
6+
import no.fdk.referencedata.mobility.vocabulary.MobilityConditionsVocabulary;
7+
import org.apache.jena.rdf.model.Model;
8+
import org.apache.jena.rdf.model.Resource;
9+
import org.apache.jena.vocabulary.OWL;
10+
import org.apache.jena.vocabulary.SKOS;
11+
import org.springframework.stereotype.Component;
12+
import reactor.core.publisher.Flux;
13+
import reactor.core.publisher.Mono;
14+
15+
import java.util.Arrays;
16+
import java.util.HashMap;
17+
import java.util.List;
18+
import java.util.Map;
19+
import java.util.stream.Collectors;
20+
21+
@Component
22+
@Slf4j
23+
public class MobilityConditionHarvester extends AbstractMobilityHarvester<MobilityCondition> {
24+
25+
private static final List<String> SUPPORTED_LANGUAGES =
26+
Arrays.stream(Language.values())
27+
.map(Language::code)
28+
.collect(Collectors.toList());
29+
private static final String PATH = "conditions-for-access-and-usage/latest/conditions-for-access-and-usage.ttl";
30+
private static String VERSION = "0";
31+
32+
public MobilityConditionHarvester() {
33+
super();
34+
}
35+
36+
public String getVersion() {
37+
return VERSION;
38+
}
39+
40+
public Flux<MobilityCondition> harvest() {
41+
log.info("Starting harvest of conditions for access and usage");
42+
final org.springframework.core.io.Resource rdfSource = getSource(PATH);
43+
if(rdfSource == null) {
44+
return Flux.error(new Exception("Unable to fetch conditions for access and usage source"));
45+
}
46+
47+
loadModel(rdfSource);
48+
49+
return Mono.justOrEmpty(getModel())
50+
.doOnSuccess(this::updateVersion)
51+
.flatMapIterable(m -> m.listSubjectsWithProperty(SKOS.inScheme,
52+
MobilityConditionsVocabulary.SCHEME).toList())
53+
.filter(Resource::isURIResource)
54+
.map(this::mapMobilityCondition);
55+
}
56+
57+
private void updateVersion(Model m) {
58+
VERSION = m.getProperty(
59+
m.getResource(MobilityConditionsVocabulary.getURI()),
60+
OWL.versionInfo
61+
).getString();
62+
}
63+
64+
private MobilityCondition mapMobilityCondition(Resource condition) {
65+
final Map<String, String> label = new HashMap<>();
66+
Flux.fromIterable(condition.listProperties(SKOS.prefLabel).toList())
67+
.map(stmt -> stmt.getObject().asLiteral())
68+
.filter(literal -> SUPPORTED_LANGUAGES.contains(literal.getLanguage()))
69+
.doOnNext(literal -> label.put(literal.getLanguage(), literal.getString()))
70+
.subscribe();
71+
72+
String[] uriParts = condition.getURI().split("/");
73+
74+
return MobilityCondition.builder()
75+
.uri(condition.getURI())
76+
.code(uriParts[uriParts.length - 1])
77+
.label(label)
78+
.build();
79+
}
80+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package no.fdk.referencedata.mobility.conditions;
2+
3+
import org.springframework.data.repository.CrudRepository;
4+
5+
import java.util.Optional;
6+
7+
public interface MobilityConditionRepository extends CrudRepository<MobilityCondition, String> {
8+
Optional<MobilityCondition> findByCode(String code);
9+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package no.fdk.referencedata.mobility.conditions;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
import no.fdk.referencedata.rdf.RDFSource;
5+
import no.fdk.referencedata.rdf.RDFSourceRepository;
6+
import no.fdk.referencedata.rdf.RDFUtils;
7+
import no.fdk.referencedata.settings.HarvestSettings;
8+
import no.fdk.referencedata.settings.HarvestSettingsRepository;
9+
import no.fdk.referencedata.settings.Settings;
10+
import no.fdk.referencedata.util.Version;
11+
import org.apache.jena.rdf.model.ModelFactory;
12+
import org.apache.jena.riot.Lang;
13+
import org.apache.jena.riot.RDFFormat;
14+
import org.springframework.beans.factory.annotation.Autowired;
15+
import org.springframework.stereotype.Service;
16+
import org.springframework.transaction.annotation.Transactional;
17+
18+
import java.time.LocalDateTime;
19+
import java.util.concurrent.atomic.AtomicInteger;
20+
21+
@Service
22+
@Slf4j
23+
public class MobilityConditionService {
24+
private final String dbSourceID = "mobility-condition-source";
25+
26+
private final MobilityConditionHarvester mobilityConditionHarvester;
27+
28+
private final MobilityConditionRepository mobilityConditionRepository;
29+
30+
private final HarvestSettingsRepository harvestSettingsRepository;
31+
32+
private final RDFSourceRepository rdfSourceRepository;
33+
34+
@Autowired
35+
public MobilityConditionService(MobilityConditionHarvester mobilityConditionHarvester,
36+
MobilityConditionRepository mobilityConditionRepository,
37+
RDFSourceRepository rdfSourceRepository,
38+
HarvestSettingsRepository harvestSettingsRepository) {
39+
this.mobilityConditionHarvester = mobilityConditionHarvester;
40+
this.mobilityConditionRepository = mobilityConditionRepository;
41+
this.harvestSettingsRepository = harvestSettingsRepository;
42+
this.rdfSourceRepository = rdfSourceRepository;
43+
}
44+
45+
public boolean firstTime() {
46+
return mobilityConditionRepository.count() == 0;
47+
}
48+
49+
public String getRdf(RDFFormat rdfFormat) {
50+
String source = rdfSourceRepository.findById(dbSourceID).orElse(new RDFSource()).getTurtle();
51+
if (rdfFormat == RDFFormat.TURTLE) {
52+
return source;
53+
} else {
54+
return RDFUtils.modelToResponse(ModelFactory.createDefaultModel().read(source, Lang.TURTLE.getName()), rdfFormat);
55+
}
56+
}
57+
58+
@Transactional
59+
public void harvestAndSave(boolean force) {
60+
try {
61+
final Version latestVersion = new Version(mobilityConditionHarvester.getVersion());
62+
63+
final HarvestSettings settings = harvestSettingsRepository.findById(Settings.MOBILITY_CONDITION.name())
64+
.orElse(HarvestSettings.builder()
65+
.id(Settings.MOBILITY_CONDITION.name())
66+
.latestVersion("0")
67+
.build());
68+
69+
final Version currentVersion = new Version(settings.getLatestVersion());
70+
71+
if(force || latestVersion.compareTo(currentVersion) > 0) {
72+
mobilityConditionRepository.deleteAll();
73+
74+
final AtomicInteger counter = new AtomicInteger(0);
75+
final Iterable<MobilityCondition> iterable = mobilityConditionHarvester.harvest().toIterable();
76+
iterable.forEach(item -> counter.getAndIncrement());
77+
log.info("Harvest and saving {} mobility conditions", counter.get());
78+
mobilityConditionRepository.saveAll(iterable);
79+
80+
settings.setLatestHarvestDate(LocalDateTime.now());
81+
settings.setLatestVersion(mobilityConditionHarvester.getVersion());
82+
harvestSettingsRepository.save(settings);
83+
84+
RDFSource rdfSource = new RDFSource();
85+
rdfSource.setId(dbSourceID);
86+
rdfSource.setTurtle(RDFUtils.modelToResponse(mobilityConditionHarvester.getModel(), RDFFormat.TURTLE));
87+
rdfSourceRepository.save(rdfSource);
88+
}
89+
90+
} catch(Exception e) {
91+
log.error("Unable to harvest mobility conditions", e);
92+
}
93+
}
94+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package no.fdk.referencedata.mobility.conditions;
2+
3+
import lombok.Builder;
4+
import lombok.Data;
5+
6+
import java.util.List;
7+
8+
@Data
9+
@Builder
10+
public class MobilityConditions {
11+
List<MobilityCondition> mobilityConditions;
12+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package no.fdk.referencedata.mobility.vocabulary;
2+
3+
import org.apache.jena.rdf.model.Model;
4+
import org.apache.jena.rdf.model.ModelFactory;
5+
import org.apache.jena.rdf.model.Resource;
6+
7+
public class MobilityConditionsVocabulary {
8+
private static final Model m = ModelFactory.createDefaultModel();
9+
public static final Resource NAMESPACE;
10+
public static final Resource SCHEME;
11+
12+
public static String getURI() {
13+
return "https://w3id.org/mobilitydcat-ap/conditions-for-access-and-usage";
14+
}
15+
16+
static {
17+
NAMESPACE = m.createResource(getURI() + "/");
18+
SCHEME = m.createResource(getURI());
19+
}
20+
}

src/main/java/no/fdk/referencedata/settings/Settings.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public enum Settings {
1919
LICENCE,
2020
MAIN_ACTIVITY,
2121
MEDIA_TYPE,
22+
MOBILITY_CONDITION,
2223
MOBILITY_DATA_STANDARD,
2324
MOBILITY_THEME,
2425
PLANNED_AVAILABILITY,

0 commit comments

Comments
 (0)