Skip to content

Commit

Permalink
Merge pull request #6 from starwit/feature/AB#1229-connect-to-analytics
Browse files Browse the repository at this point in the history
Feature/ab#1229 connect to analytic database
  • Loading branch information
ztarbug authored Nov 6, 2024
2 parents a307f71 + 2d5241e commit c2efff2
Show file tree
Hide file tree
Showing 12 changed files with 218 additions and 141 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/pr-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
name: "Install software"
runs-on: [self-hosted, linux, X64]

steps:
steps:
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
Expand All @@ -35,6 +35,8 @@ jobs:
run: mvn clean -B package --file pom.xml
env:
CI: false
MAVEN_GPG_PASSPHRASE: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }}
MAVEN_GPG_KEY: ${{ secrets.OSSRH_GPG_SECRET_KEY }}



48 changes: 45 additions & 3 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Open Data Platform Adapter

This software implements an adapter to Wolfsburg's open data platform. It will update available parking spots for configured parking space
This software implements an adapter to Wolfsburg's open data platform. It will update available parking spots for configured parking space.

## Functions
Adapter implements the following functions:
Expand All @@ -10,6 +10,11 @@ Adapter implements the following functions:
## Configuration
App can be configured via application.properties file. Just on parking area is supported and it's configuration is also done via application properties. Next to all Spring Boot config the following keys can be used:
```
# base path for api
rest.base-path=api
# if true update starts immediately
config.autostart=true
# user name for ODP login
odp.auth.username=meckauer
# password for ODP login
Expand All @@ -19,15 +24,52 @@ odp.auth.url=https://auth.staging.wolfsburg.digital/auth/realms/default/protocol
# URL to read & update parking space data
odp.parking.url=https://api.staging.wolfsburg.digital/context/v2/entities/
# ID for the one currently supported parking space
odp.parking.meckauer.id=OffStreetParking-Pkpd-787878
# Configure mapping to parking area
odp.parkingareaid=OffStreetParking-Pkpd-787878
analytics.observation_area=12
```

## Build & Run
Adapter is build as a Spring Boot application, that runs an update job with a fixed schedule. Building and running can be done like so:
```bash
mvn clean package
java -jar run target/odp-adapter-1.0-SNAPSHOT.jar
```

### Background Wolfsburg's Open Data Platform

Wolfsburg's open data platform follows the Fiware standard and here is some background doc, how this platform can be used.

Get access token:
```bash
curl -H application/x-www-form-urlencoded -d "realm=default" -d "client_id=api" -d "scope=entity:read entity:write" -d "username=username" -d "password=PASSWORD" -d "grant_type=password" "https://auth.staging.wolfsburg.digital/auth/realms/default/protocol/openid-connect/token"
```

Get latest OffStreetParking data
```bash
curl --location 'https://api.staging.wolfsburg.digital/context/v2/entities/OffStreetParking-Pkpd-787878/' -H 'fiware-ServicePath: /ParkingManagement' -H 'fiware-service: Wolfsburg' -H 'Authorization: Bearer TOKEN'
```

Update OffStreetParking data
```bash
curl --location --request PATCH 'https://api.staging.wolfsburg.digital/context/v2/entities/OffStreetParking-Pkpd-787878/attrs/' \
--header 'fiware-ServicePath: /ParkingManagement' \
--header 'fiware-service: Wolfsburg' \
--header 'content-type: application/json' \
-H 'Authorization: Bearer Token ' \
--data '{
"availableSpotNumber": {
"type": "Integer",
"value": 55
}
}'
```

Query ParkingSpots
```bash
curl --location 'https://api.staging.wolfsburg.digital/context/v2/entities?type=ParkingSpot' -H 'fiware-ServicePath: /ParkingManagement/Meckauer' -H 'fiware-service: Wolfsburg' -H 'Authorization: Bearer '
```

## License & Contribution
This software is published under the AGPLv3 and the license agreement can be found [here](/LICENSE). Pull requests are very much appreciated and you contributed code will be licensed as AGPLv3 as well.
File renamed without changes.
13 changes: 11 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<version>3.3.4</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
Expand All @@ -49,6 +53,11 @@
<artifactId>httpclient5</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.3</version>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
Expand Down Expand Up @@ -99,7 +108,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.5</version>
<version>3.2.7</version>
<executions>
<execution>
<id>sign-artifacts</id>
Expand Down
23 changes: 0 additions & 23 deletions src/main/java/de/starwit/odp/AdapterAPI.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
package de.starwit.odp;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import de.starwit.odp.model.OffStreetParking;

@RestController
@RequestMapping("${rest.base-path}")
public class AdapterAPI {
Expand All @@ -22,14 +17,6 @@ public class AdapterAPI {
@Autowired
ODPFunctions functions;

/**
* This service will use initial list of parking IDs, to import data from ODP.
*/
@GetMapping("/initial")
public void getParkingSpacesFromOdp(){
functions.importParkingSpaceData();
}

/**
* This service enables updating parking status to ODP.
* @return
Expand All @@ -51,14 +38,4 @@ public boolean stopDataTransfer() {
log.debug("Stopping transfer to ODP");
return true;
}

/**
* List of parking spaces, that are updated to ODP.
* @return
*/
@GetMapping("/parkingspaces")
public List<OffStreetParking> getParkingSpaces(){
return functions.getParkingSpaces();
}

}
108 changes: 27 additions & 81 deletions src/main/java/de/starwit/odp/ODPFunctions.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@
package de.starwit.odp;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
Expand All @@ -31,7 +23,8 @@
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import de.starwit.AuthTokenResponse;
import de.starwit.odp.analytics.AnalyticsRepository;
import de.starwit.odp.model.AuthTokenResponse;
import de.starwit.odp.model.OffStreetParking;
import de.starwit.odp.model.OffStreetParkingFunctions;
import jakarta.annotation.PostConstruct;
Expand All @@ -53,12 +46,13 @@ public class ODPFunctions {
@Value("${odp.parking.url}")
private String parkingSpaceUrl;

@Value("${config.autostart}")
private boolean autostart;
@Value("${odp.parkingareaid}")
private String parkingSpaceId;

private List<OffStreetParking> parkingSpaces = new ArrayList<>();
@Value("${config.autostart}")
private boolean sendUpdates;

private boolean sendUpdates = false;
private OffStreetParking ofs;

private LocalDateTime tokenTimeStamp;
private String token = null;
Expand All @@ -67,31 +61,17 @@ public class ODPFunctions {
private RestTemplate restTemplate;
private ObjectMapper mapper;

@Autowired
AnalyticsRepository repository;

@PostConstruct
public void init() {
log.info("Starting ODP Adapter, connecting to " + parkingSpaceUrl);
log.debug("loading mapping data");
parseParkingSpaceMapping("ParkingSpaceMapping.json");
if(autostart) {
importParkingSpaceData();
sendUpdates = true;
}
getAccessToken();
ofs = getDataFromODP(parkingSpaceId);
}

public void parseParkingSpaceMapping(String mappingFilename) {
ClassPathResource odpResultRes = new ClassPathResource(mappingFilename);
byte[] binaryData;
try {
binaryData = FileCopyUtils.copyToByteArray(odpResultRes.getInputStream());
String strJson = new String(binaryData, StandardCharsets.UTF_8);
ObjectMapper om = new ObjectMapper();
OffStreetParking[] parkingSpaceMapping = om.readValue(strJson,OffStreetParking[].class);
parkingSpaces = Arrays.asList(parkingSpaceMapping);
} catch (IOException e) {
log.error("Can't parse initial mapping. Not a lot will work. " + e.getMessage());
}
}

private void getAccessToken() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
Expand Down Expand Up @@ -128,41 +108,27 @@ private void checkIfTokenIsStillValid() {
// token is too old, try again to aqcuire one
if(diff > 2590000) {
log.debug("Token too old, get a new one");

getAccessToken();
}
}
}

/* ********************** Updating ODP ************************ */
@Scheduled(fixedDelay = 5000)
@Scheduled(fixedDelay = 6000)
private void updateParkingState() {
if(sendUpdates) {
log.debug("send updates to ODP");
checkIfTokenIsStillValid();
if(token != null) {
for (OffStreetParking ofs : parkingSpaces) {
if(ofs.isSynched()) {
updateParkingData(ofs);
sendOffStreetParkingUpdate(ofs);
}
if(ofs.isSynched()) {
ofs.setAvailableParkingSpots(repository.getParkedCars());
sendOffStreetParkingUpdate(ofs);
}
} else {
log.info("No valid token, can't update ODP");
}
}
}

/**
* To be replaced by getting actual data.
* @param ofs
*/
private void updateParkingData(OffStreetParking ofs) {
log.info("yet only fake random data");
Random ran = new Random();
int newAvailableSpots = ran.nextInt(ofs.getTotalSpotNumber());
ofs.setAvailableParkingSpots(newAvailableSpots);
}

private void sendOffStreetParkingUpdate(OffStreetParking ofs) {
HttpHeaders headers = new HttpHeaders();
headers.set("fiware-ServicePath", "/ParkingManagement");
Expand All @@ -171,7 +137,10 @@ private void sendOffStreetParkingUpdate(OffStreetParking ofs) {
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> request = new HttpEntity<String>(createAvailableSpotsRequestBody(ofs), headers);
ResponseEntity<String> response = restTemplate.exchange(parkingSpaceUrl + "/" + ofs.getOdpID() + "/attrs", HttpMethod.PATCH, request, String.class);
log.debug("Updated parking space " + ofs.getOdpID() + " with response code " + response.getStatusCode());
log.debug("Updated parking space " + ofs.getOdpID() + " with value " + ofs.getAvailableParkingSpots() + " with response code " + response.getStatusCode());
if(!response.getStatusCode().is2xxSuccessful()) {
log.error("Can't update parking space " + ofs.getOdpID() + " with value " + ofs.getAvailableParkingSpots() + " with response code " + response.getStatusCode());
}
}

private String createAvailableSpotsRequestBody(OffStreetParking ofs) {
Expand All @@ -186,15 +155,10 @@ private String createAvailableSpotsRequestBody(OffStreetParking ofs) {

/* ********************** Synching with ODP ************************ */

public void importParkingSpaceData() {
checkIfTokenIsStillValid();
for (OffStreetParking ofs : parkingSpaces) {
OffStreetParking tmp = getDataFromODP(ofs.getOdpID());
ofs.copyContent(tmp);
}
}

private OffStreetParking getDataFromODP(String parkingSpaceId) {
OffStreetParking offStreetParking = new OffStreetParking();
offStreetParking.setOdpID(parkingSpaceId);

HttpHeaders headers = new HttpHeaders();
headers.set("fiware-ServicePath", "/ParkingManagement");
headers.set("fiware-service","Wolfsburg");
Expand All @@ -203,32 +167,14 @@ private OffStreetParking getDataFromODP(String parkingSpaceId) {

try {
ResponseEntity<String> response = restTemplate.exchange(parkingSpaceUrl + "/" + parkingSpaceId, HttpMethod.GET, request, String.class);
OffStreetParking offStreetParking = OffStreetParkingFunctions.extractOffstreetParking(response.getBody());
offStreetParking = OffStreetParkingFunctions.extractOffstreetParking(response.getBody());
offStreetParking.setOdpID(parkingSpaceId);
offStreetParking.setSynched(true);
log.info("Get data from ODP for " + parkingSpaceId + " - " + offStreetParking.toString());
return offStreetParking;
} catch (HttpClientErrorException e) {
log.info("Can't get parking space data for " + parkingSpaceId + " with response " + e.getStatusCode());
}
return null;
}

/* ********************** Managing Parking Spaces ************************ */

public void addParkingSpace(OffStreetParking ofs) {
parkingSpaces.add(ofs);
}

public void removeParkingSpace(String parkingSpaceId) {
for (OffStreetParking ofs : parkingSpaces) {
if(ofs.getOdpID().equals(parkingSpaceId)) {
parkingSpaces.remove(ofs);
}
}
}

public List<OffStreetParking> getParkingSpaces() {
return parkingSpaces;
return offStreetParking;
}

public boolean isSendUpdates() {
Expand Down
Loading

0 comments on commit c2efff2

Please sign in to comment.