diff --git a/src/main/java/dev/wms/pwrapi/dao/parking/IParkingDAO.java b/src/main/java/dev/wms/pwrapi/dao/parking/IParkingDAO.java new file mode 100644 index 0000000..1bdce4e --- /dev/null +++ b/src/main/java/dev/wms/pwrapi/dao/parking/IParkingDAO.java @@ -0,0 +1,133 @@ +package dev.wms.pwrapi.dao.parking; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import dev.wms.pwrapi.dto.parking.ParkingWithHistory; +import dev.wms.pwrapi.dto.parking.deserialization.ParkingWithHistoryArrayElement; +import dev.wms.pwrapi.dto.parking.deserialization.ParkingWithHistoryResponse; +import dev.wms.pwrapi.utils.parking.ParkingGeneralUtils; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Repository; + +import dev.wms.pwrapi.dto.parking.deserialization.ParkingArrayElement; +import dev.wms.pwrapi.dto.parking.deserialization.ParkingResponse; +import dev.wms.pwrapi.dto.parking.Parking; +import dev.wms.pwrapi.utils.parking.exceptions.WrongResponseCode; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +@Repository +public class IParkingDAO implements ParkingDAO { + + + @Override + public ArrayList getProcessedParkingInfo() throws IOException { + + ArrayList result = new ArrayList(); + + OkHttpClient client = new OkHttpClient().newBuilder() + .build(); + MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded; charset=UTF-8"); + RequestBody body = RequestBody.create(mediaType, "o=get_parks&ts=1652019293233"); + Request request = new Request.Builder() + .url("https://iparking.pwr.edu.pl/modules/iparking/scripts/ipk_operations.php") + .method("POST", body) + .addHeader("sec-ch-ua", "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"100\", \"Google Chrome\";v=\"100\"") + .addHeader("Accept", "application/json, text/javascript, */*; q=0.01") + .addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") + .addHeader("X-Requested-With", "XMLHttpRequest") + .addHeader("sec-ch-ua-mobile", "?0") + .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36") + .addHeader("sec-ch-ua-platform", "\"Windows\"") + .addHeader("Sec-Fetch-Site", "same-origin") + .addHeader("Sec-Fetch-Mode", "cors") + .addHeader("Sec-Fetch-Dest", "empty") + .addHeader("Referer", "https://iparking.pwr.edu.pl/") + .addHeader("Origin", "https://iparking.pwr.edu.pl") + .build(); + Response response = client.newCall(request).execute(); + + ParkingResponse deserializedResponse = new ObjectMapper().readValue(response.body().string(), ParkingResponse.class); + + if (deserializedResponse.getSuccess() != 0) throw new WrongResponseCode(); + + DateTimeFormatter parkingFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + for (ParkingArrayElement parking : deserializedResponse.getPlaces()) { + + Parking toAdd = new Parking().builder() + .name(ParkingGeneralUtils.determineParking(parking.getParking_id())) + .lastUpdate(LocalDateTime.parse(parking.getCzas_pomiaru(), parkingFormatter).toString()) + .leftPlaces(Integer.valueOf(parking.getLiczba_miejsc())) + .trend(Integer.valueOf(parking.getTrend())) + .build(); + + result.add(toAdd); + } + + + return result; + } + + @Override + public List getRawParkingData() throws IOException { + + OkHttpClient client = new OkHttpClient().newBuilder() + .build(); + MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded; charset=UTF-8"); + RequestBody body = RequestBody.create(mediaType, "o=get_parks&ts=1652019293233"); + Request request = new Request.Builder() + .url("https://iparking.pwr.edu.pl/modules/iparking/scripts/ipk_operations.php") + .method("POST", body) + .addHeader("sec-ch-ua", "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"100\", \"Google Chrome\";v=\"100\"") + .addHeader("Accept", "application/json, text/javascript, */*; q=0.01") + .addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") + .addHeader("X-Requested-With", "XMLHttpRequest") + .addHeader("sec-ch-ua-mobile", "?0") + .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36") + .addHeader("sec-ch-ua-platform", "\"Windows\"") + .addHeader("Sec-Fetch-Site", "same-origin") + .addHeader("Sec-Fetch-Mode", "cors") + .addHeader("Sec-Fetch-Dest", "empty") + .addHeader("Referer", "https://iparking.pwr.edu.pl/") + .addHeader("Origin", "https://iparking.pwr.edu.pl") + // .addHeader("Cookie", "PHPSESSID=sgn0fqbs1vg9bjotuum1aha957") + .build(); + Response response = client.newCall(request).execute(); + String stringResponse = response.body().string(); + System.out.println(stringResponse); + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); + ParkingWithHistoryResponse parkingWithHistoryResponses = mapper.readValue(stringResponse, ParkingWithHistoryResponse.class); + List result = new ArrayList<>(); + DateTimeFormatter parkingFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + for(ParkingWithHistoryArrayElement parking : parkingWithHistoryResponses.getPlaces()){ + + ParkingWithHistory toAdd = ParkingWithHistory.builder() + .name(ParkingGeneralUtils.determineParking(parking.getParking_id())) + .lastUpdate(LocalDateTime.parse(parking.getCzas_pomiaru(), parkingFormatter).toString()) + .history(parseHistory(parking.getChart().getX(), parking.getChart().getData()).toString().replace("{","").replace("}", "")) + .build(); + result.add(toAdd); + } + return result; + } + + private Map parseHistory(List hours, List state){ + Map states = new TreeMap<>(); + for(int i = 0; i < hours.size(); i++){ + states.put(hours.get(i), state.get(i)); + } + return states; + } + + +} \ No newline at end of file diff --git a/src/main/java/dev/wms/pwrapi/dao/parking/ParkingDAOImpl.java b/src/main/java/dev/wms/pwrapi/dao/parking/SKDParkingDAO.java similarity index 98% rename from src/main/java/dev/wms/pwrapi/dao/parking/ParkingDAOImpl.java rename to src/main/java/dev/wms/pwrapi/dao/parking/SKDParkingDAO.java index 077b262..36288b6 100644 --- a/src/main/java/dev/wms/pwrapi/dao/parking/ParkingDAOImpl.java +++ b/src/main/java/dev/wms/pwrapi/dao/parking/SKDParkingDAO.java @@ -19,7 +19,7 @@ import okhttp3.OkHttpClient; @Repository -public class ParkingDAOImpl implements ParkingDAO { +public class SKDParkingDAO implements ParkingDAO { public static final String PARKING_WRONSKIEGO = "Parking Wrońskiego"; public static final String C_13 = "C13"; diff --git a/src/main/java/dev/wms/pwrapi/dto/parking/deserialization/ParkingArrayElement.java b/src/main/java/dev/wms/pwrapi/dto/parking/deserialization/ParkingArrayElement.java index d4b437e..6b4ae12 100644 --- a/src/main/java/dev/wms/pwrapi/dto/parking/deserialization/ParkingArrayElement.java +++ b/src/main/java/dev/wms/pwrapi/dto/parking/deserialization/ParkingArrayElement.java @@ -8,6 +8,7 @@ import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import lombok.ToString; @@ -15,6 +16,7 @@ @ToString @AllArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) +@NoArgsConstructor public class ParkingArrayElement { @JsonProperty("id") diff --git a/src/main/java/dev/wms/pwrapi/dto/parking/deserialization/ParkingResponse.java b/src/main/java/dev/wms/pwrapi/dto/parking/deserialization/ParkingResponse.java index 0dd8175..e829b10 100644 --- a/src/main/java/dev/wms/pwrapi/dto/parking/deserialization/ParkingResponse.java +++ b/src/main/java/dev/wms/pwrapi/dto/parking/deserialization/ParkingResponse.java @@ -5,10 +5,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; +import lombok.NoArgsConstructor; import lombok.ToString; @ToString @Data +@NoArgsConstructor public class ParkingResponse { @JsonProperty("success") diff --git a/src/main/java/dev/wms/pwrapi/dto/parking/deserialization/ParkingWithHistoryArrayElement.java b/src/main/java/dev/wms/pwrapi/dto/parking/deserialization/ParkingWithHistoryArrayElement.java new file mode 100644 index 0000000..24d9f63 --- /dev/null +++ b/src/main/java/dev/wms/pwrapi/dto/parking/deserialization/ParkingWithHistoryArrayElement.java @@ -0,0 +1,14 @@ +package dev.wms.pwrapi.dto.parking.deserialization; + +import lombok.Data; + +@Data +public +class ParkingWithHistoryArrayElement { + private String id; + private String parking_id; + private String czas_pomiaru; + private String liczba_miejsc; + private String trend; + private ParkingWithHistoryChart chart; +} diff --git a/src/main/java/dev/wms/pwrapi/dto/parking/deserialization/ParkingWithHistoryChart.java b/src/main/java/dev/wms/pwrapi/dto/parking/deserialization/ParkingWithHistoryChart.java new file mode 100644 index 0000000..b7bf4da --- /dev/null +++ b/src/main/java/dev/wms/pwrapi/dto/parking/deserialization/ParkingWithHistoryChart.java @@ -0,0 +1,14 @@ +package dev.wms.pwrapi.dto.parking.deserialization; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class ParkingWithHistoryChart { + @JsonProperty("x") + private List x; + @JsonProperty("data") + private List data; +} diff --git a/src/main/java/dev/wms/pwrapi/dto/parking/deserialization/ParkingWithHistoryResponse.java b/src/main/java/dev/wms/pwrapi/dto/parking/deserialization/ParkingWithHistoryResponse.java new file mode 100644 index 0000000..f7b3840 --- /dev/null +++ b/src/main/java/dev/wms/pwrapi/dto/parking/deserialization/ParkingWithHistoryResponse.java @@ -0,0 +1,13 @@ +package dev.wms.pwrapi.dto.parking.deserialization; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +public class ParkingWithHistoryResponse { + private int success; + private List places; +} diff --git a/src/main/java/dev/wms/pwrapi/service/parking/ParkingProxy.java b/src/main/java/dev/wms/pwrapi/service/parking/ParkingProxy.java index b2a08b4..51b28a0 100644 --- a/src/main/java/dev/wms/pwrapi/service/parking/ParkingProxy.java +++ b/src/main/java/dev/wms/pwrapi/service/parking/ParkingProxy.java @@ -4,7 +4,7 @@ import dev.wms.pwrapi.dto.parking.Parking; import dev.wms.pwrapi.dto.parking.ParkingWithHistory; import dev.wms.pwrapi.utils.parking.ParkingDateUtils; -import lombok.AllArgsConstructor; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import java.io.IOException; @@ -16,23 +16,29 @@ * In order to reduce stress on university servers, we use proxy design pattern so * server is called if we requested data minimum one minute before */ -@AllArgsConstructor @Component public class ParkingProxy { - private ParkingDAO parkingDAO; + + private final ParkingDAO iParkingDAO; + private final ParkingDAO skdParkingDAO; private List parkingState; private List parkingWithHistoryState; + public ParkingProxy(@Qualifier("IParkingDAO") ParkingDAO iParkingDAO, @Qualifier("SKDParkingDAO") ParkingDAO skdParkingDAO) { + this.iParkingDAO = iParkingDAO; + this.skdParkingDAO = skdParkingDAO; + } + public List getParkingState() throws IOException { if(parkingStateQualifies(parkingState)){ - parkingState = parkingDAO.getProcessedParkingInfo(); + parkingState = getProcessedParkingInfo(); } return parkingState; } public List getParkingWithHistory() throws IOException { if(parkingStateWithHistoryQualifiesForUpdate(parkingWithHistoryState)){ - parkingWithHistoryState = parkingDAO.getRawParkingData(); + parkingWithHistoryState = getRawParkingInfo(); } return parkingWithHistoryState; @@ -44,7 +50,7 @@ private boolean parkingStateQualifies(List parkingState){ } private boolean parkingStateWithHistoryQualifiesForUpdate(List parkingWithHistoryState){ - return parkingState == null || parkingWithHistoryState.isEmpty() || parseUpdateTime(parkingWithHistoryState.get(0).getLastUpdate()).isBefore( + return parkingWithHistoryState == null || parkingWithHistoryState.isEmpty() || parseUpdateTime(parkingWithHistoryState.get(0).getLastUpdate()).isBefore( ParkingDateUtils.getDateTimeInPoland().minusMinutes(1) ); } @@ -53,4 +59,36 @@ private LocalDateTime parseUpdateTime(String lastUpdate){ return LocalDateTime.parse(lastUpdate, DateTimeFormatter.ISO_LOCAL_DATE_TIME); } + private List getProcessedParkingInfo(){ + try{ + return iParkingDAO.getProcessedParkingInfo(); + } catch (Throwable t) { + return getSkdProcessedParkingInfo(); + } + } + + private List getRawParkingInfo(){ + try{ + return iParkingDAO.getRawParkingData(); + } catch (Throwable t){ + return getSkdRawParkingInfo(); + } + } + + private List getSkdRawParkingInfo(){ + try { + return skdParkingDAO.getRawParkingData(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private List getSkdProcessedParkingInfo(){ + try { + return skdParkingDAO.getProcessedParkingInfo(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } diff --git a/src/main/java/dev/wms/pwrapi/utils/parking/ParkingGeneralUtils.java b/src/main/java/dev/wms/pwrapi/utils/parking/ParkingGeneralUtils.java new file mode 100644 index 0000000..8781b66 --- /dev/null +++ b/src/main/java/dev/wms/pwrapi/utils/parking/ParkingGeneralUtils.java @@ -0,0 +1,34 @@ +package dev.wms.pwrapi.utils.parking; + +public class ParkingGeneralUtils { + + /** + * Determines parking based on ID in JSON response from parking server. Returns "Unknown Id" on unknown id + * @param id ID from JSON response + * @return Name of Parking + */ + public static String determineParking(String id){ + + switch (id) { + case "5" -> { + return "D20"; + } + case "4" -> { + return "Parking Wrońskiego"; + } + case "2" -> { + return "C13"; + } + case "6" -> { + return "Geocentrum"; + } + case "7" -> { + return "Architektura"; + } + } + + return "Unknown parking id: " + id; + + } + +} \ No newline at end of file diff --git a/src/test/java/dev/wms/pwrapi/parking/ParkingDAOTest.java b/src/test/java/dev/wms/pwrapi/parking/ParkingDAOTest.java index 0344ab6..a962ffa 100644 --- a/src/test/java/dev/wms/pwrapi/parking/ParkingDAOTest.java +++ b/src/test/java/dev/wms/pwrapi/parking/ParkingDAOTest.java @@ -15,7 +15,7 @@ public class ParkingDAOTest { @Autowired - private ParkingDAO parkingDAO; + private ParkingDAO SKDParkingDAO; /** * Because of migration to SKD service, trend feature is no longer supported, and trend should be equal @@ -23,8 +23,8 @@ public class ParkingDAOTest { * @throws IOException */ @Test - public void trendShouldAlwaysBeZero() throws IOException { - List result = parkingDAO.getProcessedParkingInfo(); + public void trendShouldAlwaysBeZeroFOr() throws IOException { + List result = SKDParkingDAO.getProcessedParkingInfo(); for(Parking parking : result){ assertEquals(0, parking.getTrend()); } diff --git a/src/test/java/dev/wms/pwrapi/parking/ParkingProxySwitchingTest.java b/src/test/java/dev/wms/pwrapi/parking/ParkingProxySwitchingTest.java new file mode 100644 index 0000000..81cd97b --- /dev/null +++ b/src/test/java/dev/wms/pwrapi/parking/ParkingProxySwitchingTest.java @@ -0,0 +1,118 @@ +package dev.wms.pwrapi.parking; + +import dev.wms.pwrapi.dao.parking.ParkingDAO; +import dev.wms.pwrapi.service.parking.ParkingProxy; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.io.IOException; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +@SpringBootTest +public class ParkingProxySwitchingTest { + + @Autowired + private ParkingProxy parkingProxy; + @MockBean(name = "SKDParkingDAO") + private ParkingDAO SKDParkingDAO; + @MockBean(name = "IParkingDAO") + private ParkingDAO IParkingDAO; + + @Test + public void parkingProxyShouldCallSkdServiceIfIparkingIsUnavailable() throws IOException { + when(IParkingDAO.getProcessedParkingInfo()) + .thenThrow(new RuntimeException("Cannot access university system!")); + when(SKDParkingDAO.getProcessedParkingInfo()) + .thenReturn(Collections.emptyList()); + + parkingProxy.getParkingState(); + + verify(SKDParkingDAO, times(1)) + .getProcessedParkingInfo(); + } + + @Test + public void parkingProxyShouldCallSkdServiceIfIparkingIsUnavailableForRawData() throws IOException { + when(IParkingDAO.getRawParkingData()) + .thenThrow(new RuntimeException("Cannot access university system!")); + when(SKDParkingDAO.getRawParkingData()) + .thenReturn(Collections.emptyList()); + + parkingProxy.getParkingWithHistory(); + + verify(SKDParkingDAO, times(1)) + .getRawParkingData(); + } + + + @Test + public void parkingProxyShouldCallIparkingServiceIfIparkingIsAvailable() throws IOException { + when(IParkingDAO.getProcessedParkingInfo()) + .thenReturn(Collections.emptyList()); + when(SKDParkingDAO.getProcessedParkingInfo()) + .thenThrow(new RuntimeException("I shouldn't have been thrown...")); + + parkingProxy.getParkingState(); + + verify(IParkingDAO, times(1)) + .getProcessedParkingInfo(); + + verifyNoInteractions(SKDParkingDAO); + } + + @Test + public void parkingProxyShouldCallIparkingServiceIfIparkingIsAvailableForHistoryEndpoint() throws IOException { + when(IParkingDAO.getRawParkingData()) + .thenReturn(Collections.emptyList()); + when(SKDParkingDAO.getRawParkingData()) + .thenThrow(new RuntimeException("I shouldn't have been thrown...")); + + parkingProxy.getParkingWithHistory(); + + verify(IParkingDAO, times(1)) + .getRawParkingData(); + + verifyNoInteractions(SKDParkingDAO); + } + + + @Test + public void parkingProxyShouldThrowOnBothServicesunavailable() throws IOException { + when(IParkingDAO.getProcessedParkingInfo()) + .thenThrow(new RuntimeException("I'm not working")); + when(SKDParkingDAO.getProcessedParkingInfo()) + .thenThrow(new RuntimeException("I'm not working")); + + assertThrows(Throwable.class, () -> parkingProxy.getParkingState()); + + verify(IParkingDAO, times(1)) + .getProcessedParkingInfo(); + + verify(SKDParkingDAO, times(1)) + .getProcessedParkingInfo(); + } + + @Test + public void parkingProxyShouldThrowOnBothServicesunavailableForHistoryEndpoint() throws IOException { + when(IParkingDAO.getRawParkingData()) + .thenThrow(new RuntimeException("I'm not working")); + when(SKDParkingDAO.getRawParkingData()) + .thenThrow(new RuntimeException("I'm not working")); + + assertThrows(Throwable.class, () -> parkingProxy.getParkingWithHistory()); + + verify(IParkingDAO, times(1)) + .getRawParkingData(); + + verify(SKDParkingDAO, times(1)) + .getRawParkingData(); + } + + +} diff --git a/src/test/java/dev/wms/pwrapi/parking/ParkingProxyTest.java b/src/test/java/dev/wms/pwrapi/parking/ParkingProxyTest.java index 702889d..a6c7136 100644 --- a/src/test/java/dev/wms/pwrapi/parking/ParkingProxyTest.java +++ b/src/test/java/dev/wms/pwrapi/parking/ParkingProxyTest.java @@ -6,7 +6,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; - import java.io.IOException; import java.util.List; @@ -14,7 +13,6 @@ @SpringBootTest public class ParkingProxyTest { - @Autowired private ParkingProxy parkingProxy; @@ -30,8 +28,4 @@ public void parkingProxyShouldntUpdateDataForOneMinute() throws IOException { } } - - - - } diff --git a/src/test/java/dev/wms/pwrapi/parking/ParkingTests.java b/src/test/java/dev/wms/pwrapi/parking/ParkingTests.java index 263afeb..e06536f 100644 --- a/src/test/java/dev/wms/pwrapi/parking/ParkingTests.java +++ b/src/test/java/dev/wms/pwrapi/parking/ParkingTests.java @@ -3,6 +3,7 @@ import io.restassured.http.ContentType; import org.hamcrest.Matcher; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @@ -65,6 +66,7 @@ public void detailsEndpointShouldContainAllParkingNames(){ } @Test + @Disabled("Disabled because of integrating both iParking and SKD service") public void historyEndpointShouldBeAnArrayWrappedInString(){ get("api/parking/raw").then() .body("[0].history", getArrayMatcher())