diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..f6b46bfd97 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + runs-on: windows-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + distribution: 'adopt' + java-version: '17' + + - name: Install local dependencies + run: | + mvn install:install-file "-Dfile=libs/gpsUtil.jar" "-DgroupId=gpsUtil" "-DartifactId=gpsUtil" "-Dversion=1.0.0" "-Dpackaging=jar" + mvn install:install-file "-Dfile=libs/RewardCentral.jar" "-DgroupId=rewardCentral" "-DartifactId=rewardCentral" "-Dversion=1.0.0" "-Dpackaging=jar" + mvn install:install-file "-Dfile=libs/TripPricer.jar" "-DgroupId=tripPricer" "-DartifactId=tripPricer" "-Dversion=1.0.0" "-Dpackaging=jar" + + - name: Build with Maven + run: mvn -B package + + - name: Upload artifacts + uses: actions/upload-artifact@v2 + with: + name: my-artifacts + path: target/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..549e00a2a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000..cb28b0e37c Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000..5f0536eb74 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/TourGuide/.github/workflows/ci.yml b/TourGuide/.github/workflows/ci.yml new file mode 100644 index 0000000000..f6b46bfd97 --- /dev/null +++ b/TourGuide/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + runs-on: windows-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + distribution: 'adopt' + java-version: '17' + + - name: Install local dependencies + run: | + mvn install:install-file "-Dfile=libs/gpsUtil.jar" "-DgroupId=gpsUtil" "-DartifactId=gpsUtil" "-Dversion=1.0.0" "-Dpackaging=jar" + mvn install:install-file "-Dfile=libs/RewardCentral.jar" "-DgroupId=rewardCentral" "-DartifactId=rewardCentral" "-Dversion=1.0.0" "-Dpackaging=jar" + mvn install:install-file "-Dfile=libs/TripPricer.jar" "-DgroupId=tripPricer" "-DartifactId=tripPricer" "-Dversion=1.0.0" "-Dpackaging=jar" + + - name: Build with Maven + run: mvn -B package + + - name: Upload artifacts + uses: actions/upload-artifact@v2 + with: + name: my-artifacts + path: target/ diff --git a/TourGuide/src/main/java/com/openclassrooms/tourguide/TourGuideController.java b/TourGuide/src/main/java/com/openclassrooms/tourguide/TourGuideController.java index a884e6590b..62618f6d4c 100644 --- a/TourGuide/src/main/java/com/openclassrooms/tourguide/TourGuideController.java +++ b/TourGuide/src/main/java/com/openclassrooms/tourguide/TourGuideController.java @@ -1,19 +1,17 @@ package com.openclassrooms.tourguide; import java.util.List; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; - import gpsUtil.location.Attraction; import gpsUtil.location.VisitedLocation; +import com.openclassrooms.tourguide.dto.AttractionInfo; import com.openclassrooms.tourguide.service.TourGuideService; import com.openclassrooms.tourguide.user.User; import com.openclassrooms.tourguide.user.UserReward; - import tripPricer.Provider; @RestController @@ -41,11 +39,20 @@ public VisitedLocation getLocation(@RequestParam String userName) { // The distance in miles between the user's location and each of the attractions. // The reward points for visiting each Attraction. // Note: Attraction reward points can be gathered from RewardsCentral + + /* @RequestMapping("/getNearbyAttractions") public List getNearbyAttractions(@RequestParam String userName) { VisitedLocation visitedLocation = tourGuideService.getUserLocation(getUser(userName)); return tourGuideService.getNearByAttractions(visitedLocation); } + */ + + @RequestMapping("/getNearbyAttractions") + public List getNearbyAttractions(@RequestParam String userName) { + VisitedLocation visitedLocation = tourGuideService.getUserLocation(getUser(userName)); + return tourGuideService.getNearByAttractions(visitedLocation, getUser(userName)); + } @RequestMapping("/getRewards") public List getRewards(@RequestParam String userName) { @@ -60,6 +67,4 @@ public List getTripDeals(@RequestParam String userName) { private User getUser(String userName) { return tourGuideService.getUser(userName); } - - -} \ No newline at end of file +} diff --git a/TourGuide/src/main/java/com/openclassrooms/tourguide/dto/AttractionInfo.java b/TourGuide/src/main/java/com/openclassrooms/tourguide/dto/AttractionInfo.java new file mode 100644 index 0000000000..ab335fd755 --- /dev/null +++ b/TourGuide/src/main/java/com/openclassrooms/tourguide/dto/AttractionInfo.java @@ -0,0 +1,78 @@ +package com.openclassrooms.tourguide.dto; + +public class AttractionInfo { + private String name; + private double attractionLatitude; + private double attractionLongitude; + private double userLatitude; + private double userLongitude; + private double distance; + private int rewardPoints; + + public AttractionInfo(String name, double attractionLatitude, double attractionLongitude, + double userLatitude, double userLongitude, double distance, int rewardPoints) { + this.name = name; + this.attractionLatitude = attractionLatitude; + this.attractionLongitude = attractionLongitude; + this.userLatitude = userLatitude; + this.userLongitude = userLongitude; + this.distance = distance; + this.rewardPoints = rewardPoints; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getAttractionLatitude() { + return attractionLatitude; + } + + public void setAttractionLatitude(double attractionLatitude) { + this.attractionLatitude = attractionLatitude; + } + + public double getAttractionLongitude() { + return attractionLongitude; + } + + public void setAttractionLongitude(double attractionLongitude) { + this.attractionLongitude = attractionLongitude; + } + + public double getUserLatitude() { + return userLatitude; + } + + public void setUserLatitude(double userLatitude) { + this.userLatitude = userLatitude; + } + + public double getUserLongitude() { + return userLongitude; + } + + public void setUserLongitude(double userLongitude) { + this.userLongitude = userLongitude; + } + + public double getDistance() { + return distance; + } + + public void setDistance(double distance) { + this.distance = distance; + } + + public int getRewardPoints() { + return rewardPoints; + } + + public void setRewardPoints(int rewardPoints) { + this.rewardPoints = rewardPoints; + } +} diff --git a/TourGuide/src/main/java/com/openclassrooms/tourguide/service/RewardsService.java b/TourGuide/src/main/java/com/openclassrooms/tourguide/service/RewardsService.java index ad440eb484..4e07790044 100644 --- a/TourGuide/src/main/java/com/openclassrooms/tourguide/service/RewardsService.java +++ b/TourGuide/src/main/java/com/openclassrooms/tourguide/service/RewardsService.java @@ -1,9 +1,10 @@ package com.openclassrooms.tourguide.service; +import java.util.ArrayList; import java.util.List; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Service; import gpsUtil.GpsUtil; import gpsUtil.location.Attraction; import gpsUtil.location.Location; @@ -11,6 +12,9 @@ import rewardCentral.RewardCentral; import com.openclassrooms.tourguide.user.User; import com.openclassrooms.tourguide.user.UserReward; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; @Service public class RewardsService { @@ -23,6 +27,13 @@ public class RewardsService { private final GpsUtil gpsUtil; private final RewardCentral rewardsCentral; + private static final int THREAD_POOL_SIZE = 50; + + + public static int getThreadPoolSize() { + return THREAD_POOL_SIZE; + } + public RewardsService(GpsUtil gpsUtil, RewardCentral rewardCentral) { this.gpsUtil = gpsUtil; this.rewardsCentral = rewardCentral; @@ -36,21 +47,94 @@ public void setDefaultProximityBuffer() { proximityBuffer = defaultProximityBuffer; } + + // parcourir la liste des emplacements visités par l'utilisateur (userLocations) + // et des attractions, tout en modifiant la liste des récompenses de l'utilisateur (user.getUserRewards()) + // vous modifiez la liste user.getUserRewards() en ajoutant des éléments à l'intérieur de la boucle \ + + /* public void calculateRewards(User user) { - List userLocations = user.getVisitedLocations(); + // List userLocations = user.getVisitedLocations(); + List userLocations = new ArrayList<>(user.getVisitedLocations()); List attractions = gpsUtil.getAttractions(); - for(VisitedLocation visitedLocation : userLocations) { - for(Attraction attraction : attractions) { - if(user.getUserRewards().stream().filter(r -> r.attraction.attractionName.equals(attraction.attractionName)).count() == 0) { - if(nearAttraction(visitedLocation, attraction)) { - user.addUserReward(new UserReward(visitedLocation, attraction, getRewardPoints(attraction, user))); + List newRewards = new ArrayList(); + for (VisitedLocation visitedLocation : userLocations) { + + for (Attraction attraction : attractions) { + //if (user.getUserRewards().stream().filter(r -> r.attraction.attractionName.equals(attraction.attractionName)).count() == 0) { + if (newRewards.stream().filter(r -> r.attraction.attractionName.equals(attraction.attractionName)).count() == 0) { + if (nearAttraction(visitedLocation, attraction)) { + // user.addUserReward(new UserReward(visitedLocation, attraction, getRewardPoints(attraction, user))); + newRewards.add(new UserReward(visitedLocation, attraction, getRewardPoints(attraction, user))); } } } - } + } + user.setUserRewards(newRewards); + } + + + */ + public void calculateRewards(User user) { + List userLocations = new ArrayList<>(user.getVisitedLocations()); + List attractions = gpsUtil.getAttractions(); + + List newRewards = new ArrayList<>(); + + userLocations.stream() + // Applique une fonction à chaque élément du flux (chaque VisitedLocation), produisant un flux interne + // pour chaque VisitedLocation. Le flux interne est créé à partir des attractions + // qui sont proches de la VisitedLocation actuelle + .flatMap(visitedLocation -> + attractions.stream() + .filter(attraction -> nearAttraction(visitedLocation, attraction)) + .filter(attraction -> newRewards.stream() + // si aucun élément du flux newRewards correspond à l'attraction actuelle + .noneMatch(r -> r.attraction.attractionName.equals(attraction.attractionName))) + // Transforme chaque attraction filtrée en un objet UserReward + .map(attraction -> new UserReward(visitedLocation, attraction, getRewardPoints(attraction, user))) + ) + // Ajoute chaque UserReward résultant à la liste newRewards + .forEach(newRewards::add); + + user.setUserRewards(newRewards); } + + + + // Un pool de 50 threads est créé. + // Ensuite, une tâche est soumise à ce pool pour chaque utilisateur dans la liste users. + // Ces tâches sont exécutées en parallèle, avec un maximum de 50 tâches simultanées + // à un moment donné, grâce au pool de threads + public void calculateRewardsAsync(List users) { + // Créez un ExecutorService avec le nombre souhaité de threads + ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE); + + // Créez un CountDownLatch avec un compte égal au nombre d'utilisateurs + CountDownLatch countDownLatch = new CountDownLatch(users.size()); + + // Soumettez des tâches pour chaque utilisateur + for (User user : users) { + executorService.submit(() -> { + // System.out.println(user.getEmailAddress()+" performed by "+ Thread.currentThread().getName()); + calculateRewards(user); + countDownLatch.countDown(); // Diminuez le compte lorsque la tâche est terminée + }); + } + + // Attendez que toutes les tâches soient terminées (besoin du test) + try { + countDownLatch.await(); // Cela attend indéfiniment jusqu'à ce que toutes les tâches soient terminées + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // Arrêtez l'ExecutorService + executorService.shutdown(); + } + public boolean isWithinAttractionProximity(Attraction attraction, Location location) { return getDistance(attraction, location) > attractionProximityRange ? false : true; } @@ -59,7 +143,7 @@ private boolean nearAttraction(VisitedLocation visitedLocation, Attraction attra return getDistance(attraction, visitedLocation.location) > proximityBuffer ? false : true; } - private int getRewardPoints(Attraction attraction, User user) { + public int getRewardPoints(Attraction attraction, User user) { return rewardsCentral.getAttractionRewardPoints(attraction.attractionId, user.getUserId()); } @@ -76,5 +160,4 @@ public double getDistance(Location loc1, Location loc2) { double statuteMiles = STATUTE_MILES_PER_NAUTICAL_MILE * nauticalMiles; return statuteMiles; } - } diff --git a/TourGuide/src/main/java/com/openclassrooms/tourguide/service/TourGuideService.java b/TourGuide/src/main/java/com/openclassrooms/tourguide/service/TourGuideService.java index 1aa6472dc9..2d1d5a1625 100644 --- a/TourGuide/src/main/java/com/openclassrooms/tourguide/service/TourGuideService.java +++ b/TourGuide/src/main/java/com/openclassrooms/tourguide/service/TourGuideService.java @@ -1,13 +1,14 @@ package com.openclassrooms.tourguide.service; +import com.openclassrooms.tourguide.dto.AttractionInfo; import com.openclassrooms.tourguide.helper.InternalTestHelper; import com.openclassrooms.tourguide.tracker.Tracker; import com.openclassrooms.tourguide.user.User; import com.openclassrooms.tourguide.user.UserReward; - import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.ArrayList; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -15,20 +16,22 @@ import java.util.Map; import java.util.Random; import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.stream.Collectors; import java.util.stream.IntStream; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; - import gpsUtil.GpsUtil; import gpsUtil.location.Attraction; import gpsUtil.location.Location; import gpsUtil.location.VisitedLocation; - import tripPricer.Provider; import tripPricer.TripPricer; +import rewardCentral.RewardCentral; + @Service public class TourGuideService { @@ -79,6 +82,13 @@ public void addUser(User user) { } } + /*effectue des calculs basés sur les points de récompense de + l'utilisateur, puis appelle un service externe (tripPricer) pour obtenir + des offres de voyage en fonction des préférences de l'utilisateur + Les offres sont ensuite associées à l'utilisateur et renvoyées */ + + + public List getTripDeals(User user) { int cumulatativeRewardPoints = user.getUserRewards().stream().mapToInt(i -> i.getRewardPoints()).sum(); List providers = tripPricer.getPrice(tripPricerApiKey, user.getUserId(), @@ -94,10 +104,40 @@ public VisitedLocation trackUserLocation(User user) { rewardsService.calculateRewards(user); return visitedLocation; } + + + public void trackUsersLocationAsync(List users) { + // Créez un ExecutorService avec le nombre souhaité de threads + ExecutorService executorService = Executors.newFixedThreadPool(RewardsService.getThreadPoolSize()); + + // Créez un CountDownLatch avec un compte égal au nombre d'utilisateurs + CountDownLatch countDownLatch = new CountDownLatch(users.size()); + + // Soumettez des tâches pour chaque utilisateur + for (User user : users) { + executorService.submit(() -> { + trackUserLocation(user); + countDownLatch.countDown(); // Diminuez le compte lorsque la tâche est terminée + }); + } + // Attendez que toutes les tâches soient terminées + try { + countDownLatch.await(); // Cela attend indéfiniment jusqu'à ce que toutes les tâches soient terminées (besoin du test) + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // Arrêtez l'ExecutorService + executorService.shutdown(); + } + + + /* public List getNearByAttractions(VisitedLocation visitedLocation) { List nearbyAttractions = new ArrayList<>(); for (Attraction attraction : gpsUtil.getAttractions()) { + // Vérifie si l'attraction est dans la proximité de la localisation visitée par l'utilisateur if (rewardsService.isWithinAttractionProximity(attraction, visitedLocation.location)) { nearbyAttractions.add(attraction); } @@ -105,6 +145,59 @@ public List getNearByAttractions(VisitedLocation visitedLocation) { return nearbyAttractions; } + */ + + public List getNearByAttractions(VisitedLocation visitedLocation, User user) { + List allAttractions = gpsUtil.getAttractions(); + // Trier la liste allAttractions en fonction de la distance entre chaque attraction et la dernière localisation de l'utilisateur + allAttractions.sort( + Comparator.comparingDouble(attraction -> rewardsService.getDistance(attraction, visitedLocation.location))); + + // Obtenir les 5 attractions les plus proches + List nearbyAttractions = allAttractions.stream().limit(5).collect(Collectors.toList()); + + // Créer une liste d'objets AttractionInfo pour contenir les informations demandées + List result = new ArrayList<>(); + + // Remplir la liste avec les informations de chaque attraction + for (Attraction attraction : nearbyAttractions) { + double distance = rewardsService.getDistance(attraction, visitedLocation.location); + + int rewardPoints = rewardsService.getRewardPoints(attraction, user); + + AttractionInfo attractionInfo = new AttractionInfo( + attraction.attractionName, + attraction.latitude, + attraction.longitude, + visitedLocation.location.latitude, + visitedLocation.location.longitude, + distance, + rewardPoints + ); + + result.add(attractionInfo); + } + + return result; + } + + /* +public List getNearByAttractions(VisitedLocation visitedLocation) { + List allAttractions = gpsUtil.getAttractions(); + + // Trier la liste allAttractions en fonction de la distance entre chaque attraction et la dernière localisation de l'utilisateur + + allAttractions.sort( + Comparator.comparingDouble(attraction -> rewardsService.getDistance(attraction, visitedLocation.location))); + + // Obtenir les 5 attractions les plus proches + + List nearbyAttractions = allAttractions.stream().limit(5).collect(Collectors.toList()); + + return nearbyAttractions; +} + +*/ private void addShutDownHook() { Runtime.getRuntime().addShutdownHook(new Thread() { @@ -160,5 +253,4 @@ private Date getRandomTime() { LocalDateTime localDateTime = LocalDateTime.now().minusDays(new Random().nextInt(30)); return Date.from(localDateTime.toInstant(ZoneOffset.UTC)); } - } diff --git a/TourGuide/src/main/java/com/openclassrooms/tourguide/user/User.java b/TourGuide/src/main/java/com/openclassrooms/tourguide/user/User.java index 32ed3b14ea..10e0bf4359 100644 --- a/TourGuide/src/main/java/com/openclassrooms/tourguide/user/User.java +++ b/TourGuide/src/main/java/com/openclassrooms/tourguide/user/User.java @@ -69,12 +69,21 @@ public void clearVisitedLocations() { visitedLocations.clear(); } + // un UserReward avec la même attraction n'est pas ajouté plusieurs fois à la liste + + // exclure tous les éléments qui ont le même nom d'attraction que l'objet userReward actuel + public void addUserReward(UserReward userReward) { if(userRewards.stream().filter(r -> !r.attraction.attractionName.equals(userReward.attraction)).count() == 0) { userRewards.add(userReward); } } + + public void setUserRewards(List userRewards) { + this.userRewards = userRewards; + } + public List getUserRewards() { return userRewards; } diff --git a/TourGuide/src/test/java/com/openclassrooms/tourguide/TestPerformance.java b/TourGuide/src/test/java/com/openclassrooms/tourguide/TestPerformance.java index aed028f861..2bed829dd8 100644 --- a/TourGuide/src/test/java/com/openclassrooms/tourguide/TestPerformance.java +++ b/TourGuide/src/test/java/com/openclassrooms/tourguide/TestPerformance.java @@ -45,14 +45,16 @@ public class TestPerformance { * TimeUnit.MILLISECONDS.toSeconds(stopWatch.getTime())); */ - @Disabled + //@Disabled @Test public void highVolumeTrackLocation() { GpsUtil gpsUtil = new GpsUtil(); RewardsService rewardsService = new RewardsService(gpsUtil, new RewardCentral()); // Users should be incremented up to 100,000, and test finishes within 15 // minutes - InternalTestHelper.setInternalUserNumber(100); + //InternalTestHelper.setInternalUserNumber(100); + InternalTestHelper.setInternalUserNumber(InternalTestHelper.getInternalUserNumber()); + TourGuideService tourGuideService = new TourGuideService(gpsUtil, rewardsService); List allUsers = new ArrayList<>(); @@ -60,9 +62,15 @@ public void highVolumeTrackLocation() { StopWatch stopWatch = new StopWatch(); stopWatch.start(); + + /* for (User user : allUsers) { tourGuideService.trackUserLocation(user); } + */ + + tourGuideService.trackUsersLocationAsync(allUsers); + stopWatch.stop(); tourGuideService.tracker.stopTracking(); @@ -71,7 +79,8 @@ public void highVolumeTrackLocation() { assertTrue(TimeUnit.MINUTES.toSeconds(15) >= TimeUnit.MILLISECONDS.toSeconds(stopWatch.getTime())); } - @Disabled + +// @Disabled @Test public void highVolumeGetRewards() { GpsUtil gpsUtil = new GpsUtil(); @@ -79,7 +88,9 @@ public void highVolumeGetRewards() { // Users should be incremented up to 100,000, and test finishes within 20 // minutes - InternalTestHelper.setInternalUserNumber(100); + //InternalTestHelper.setInternalUserNumber(100); + InternalTestHelper.setInternalUserNumber(InternalTestHelper.getInternalUserNumber()); + StopWatch stopWatch = new StopWatch(); stopWatch.start(); TourGuideService tourGuideService = new TourGuideService(gpsUtil, rewardsService); @@ -89,8 +100,10 @@ public void highVolumeGetRewards() { allUsers = tourGuideService.getAllUsers(); allUsers.forEach(u -> u.addToVisitedLocations(new VisitedLocation(u.getUserId(), attraction, new Date()))); - allUsers.forEach(u -> rewardsService.calculateRewards(u)); + // allUsers.forEach(u -> rewardsService.calculateRewards(u)); + rewardsService.calculateRewardsAsync(allUsers); + for (User user : allUsers) { assertTrue(user.getUserRewards().size() > 0); } @@ -101,5 +114,4 @@ public void highVolumeGetRewards() { + " seconds."); assertTrue(TimeUnit.MINUTES.toSeconds(20) >= TimeUnit.MILLISECONDS.toSeconds(stopWatch.getTime())); } - } diff --git a/TourGuide/src/test/java/com/openclassrooms/tourguide/TestTourGuideService.java b/TourGuide/src/test/java/com/openclassrooms/tourguide/TestTourGuideService.java index 2b053739e2..b2a58def20 100644 --- a/TourGuide/src/test/java/com/openclassrooms/tourguide/TestTourGuideService.java +++ b/TourGuide/src/test/java/com/openclassrooms/tourguide/TestTourGuideService.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Date; import java.util.List; import java.util.UUID; @@ -11,13 +12,20 @@ import gpsUtil.GpsUtil; import gpsUtil.location.Attraction; +import gpsUtil.location.Location; import gpsUtil.location.VisitedLocation; import rewardCentral.RewardCentral; + +import com.openclassrooms.tourguide.dto.AttractionInfo; import com.openclassrooms.tourguide.helper.InternalTestHelper; import com.openclassrooms.tourguide.service.RewardsService; import com.openclassrooms.tourguide.service.TourGuideService; import com.openclassrooms.tourguide.user.User; import tripPricer.Provider; +import com.openclassrooms.tourguide.user.UserReward; + +import gpsUtil.location.Attraction; +import gpsUtil.location.VisitedLocation; public class TestTourGuideService { @@ -92,37 +100,38 @@ public void trackUser() { assertEquals(user.getUserId(), visitedLocation.userId); } - @Disabled // Not yet implemented + // @Disabled // Not yet implemented + + // les 5 attractions les plus proches de la dernière localisation de + // l'utilisateur + @Test public void getNearbyAttractions() { GpsUtil gpsUtil = new GpsUtil(); RewardsService rewardsService = new RewardsService(gpsUtil, new RewardCentral()); - InternalTestHelper.setInternalUserNumber(0); + InternalTestHelper.setInternalUserNumber(1); TourGuideService tourGuideService = new TourGuideService(gpsUtil, rewardsService); User user = new User(UUID.randomUUID(), "jon", "000", "jon@tourGuide.com"); VisitedLocation visitedLocation = tourGuideService.trackUserLocation(user); - - List attractions = tourGuideService.getNearByAttractions(visitedLocation); + + List attractions = tourGuideService.getNearByAttractions(visitedLocation, user); tourGuideService.tracker.stopTracking(); assertEquals(5, attractions.size()); } + + @Test public void getTripDeals() { GpsUtil gpsUtil = new GpsUtil(); RewardsService rewardsService = new RewardsService(gpsUtil, new RewardCentral()); InternalTestHelper.setInternalUserNumber(0); TourGuideService tourGuideService = new TourGuideService(gpsUtil, rewardsService); - User user = new User(UUID.randomUUID(), "jon", "000", "jon@tourGuide.com"); - List providers = tourGuideService.getTripDeals(user); - tourGuideService.tracker.stopTracking(); - - assertEquals(10, providers.size()); + assertEquals(5, providers.size()); } - } diff --git a/libs/RewardCentral.jar b/libs/RewardCentral.jar new file mode 100644 index 0000000000..28c99e5649 Binary files /dev/null and b/libs/RewardCentral.jar differ diff --git a/libs/TripPricer.jar b/libs/TripPricer.jar new file mode 100644 index 0000000000..d07b668174 Binary files /dev/null and b/libs/TripPricer.jar differ diff --git a/libs/gpsUtil.jar b/libs/gpsUtil.jar new file mode 100644 index 0000000000..2f3ec1ff03 Binary files /dev/null and b/libs/gpsUtil.jar differ diff --git a/mvnw b/mvnw new file mode 100644 index 0000000000..66df285428 --- /dev/null +++ b/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000000..95ba6f54ac --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000..83fb68191e --- /dev/null +++ b/pom.xml @@ -0,0 +1,93 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.1.1 + + + com.openclassrooms + tourguide + 0.0.1-SNAPSHOT + tourguide + Demo project for Spring Boot + + 17 + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.apache.commons + commons-lang3 + + + + org.junit.jupiter + junit-jupiter-api + test + + + gpsUtil + gpsUtil + 1.0.0 + + + tripPricer + tripPricer + 1.0.0 + + + rewardCentral + rewardCentral + 1.0.0 + + + + + + org.springframework.boot + spring-boot-maven-plugin + 3.1.1 + + com.openclassrooms.tourguide.TourguideApplication + + + + + repackage + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + + + + + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000000..ed94ac4763 --- /dev/null +++ b/readme.md @@ -0,0 +1,12 @@ +# Technologies + +> Java 17 +> Spring Boot 3.X +> JUnit 5 + +# How to have gpsUtil, rewardCentral and tripPricer dependencies available ? + +> Run : +- mvn install:install-file -Dfile=/libs/gpsUtil.jar -DgroupId=gpsUtil -DartifactId=gpsUtil -Dversion=1.0.0 -Dpackaging=jar +- mvn install:install-file -Dfile=/libs/RewardCentral.jar -DgroupId=rewardCentral -DartifactId=rewardCentral -Dversion=1.0.0 -Dpackaging=jar +- mvn install:install-file -Dfile=/libs/TripPricer.jar -DgroupId=tripPricer -DartifactId=tripPricer -Dversion=1.0.0 -Dpackaging=jar diff --git a/src/main/java/com/openclassrooms/tourguide/TourGuideController.java b/src/main/java/com/openclassrooms/tourguide/TourGuideController.java new file mode 100644 index 0000000000..371249266f --- /dev/null +++ b/src/main/java/com/openclassrooms/tourguide/TourGuideController.java @@ -0,0 +1,72 @@ +package com.openclassrooms.tourguide; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import gpsUtil.location.Attraction; +import gpsUtil.location.VisitedLocation; + +import com.openclassrooms.tourguide.dto.AttractionInfo; +import com.openclassrooms.tourguide.service.TourGuideService; +import com.openclassrooms.tourguide.user.User; +import com.openclassrooms.tourguide.user.UserReward; +import tripPricer.Provider; + +@RestController +public class TourGuideController { + + @Autowired + TourGuideService tourGuideService; + + @RequestMapping("/") + public String index() { + return "Greetings from TourGuide!"; + } + + @RequestMapping("/getLocation") + public VisitedLocation getLocation(@RequestParam String userName) { + return tourGuideService.getUserLocation(getUser(userName)); + } + + // TODO: Change this method to no longer return a List of Attractions. + // Instead: Get the closest five tourist attractions to the user - no matter how far away they are. + // Return a new JSON object that contains: + // Name of Tourist attraction, + // Tourist attractions lat/long, + // The user's location lat/long, + // The distance in miles between the user's location and each of the attractions. + // The reward points for visiting each Attraction. + // Note: Attraction reward points can be gathered from RewardsCentral + + /* + @RequestMapping("/getNearbyAttractions") + public List getNearbyAttractions(@RequestParam String userName) { + VisitedLocation visitedLocation = tourGuideService.getUserLocation(getUser(userName)); + return tourGuideService.getNearByAttractions(visitedLocation); + } + */ + + @RequestMapping("/getNearbyAttractions") + public List getNearbyAttractions(@RequestParam String userName) { + VisitedLocation visitedLocation = tourGuideService.getUserLocation(getUser(userName)); + return tourGuideService.getNearByAttractions(visitedLocation, getUser(userName)); + } + + @RequestMapping("/getRewards") + public List getRewards(@RequestParam String userName) { + return tourGuideService.getUserRewards(getUser(userName)); + } + + @RequestMapping("/getTripDeals") + public List getTripDeals(@RequestParam String userName) { + return tourGuideService.getTripDeals(getUser(userName)); + } + + private User getUser(String userName) { + return tourGuideService.getUser(userName); + } + + +} \ No newline at end of file diff --git a/src/main/java/com/openclassrooms/tourguide/TourGuideModule.java b/src/main/java/com/openclassrooms/tourguide/TourGuideModule.java new file mode 100644 index 0000000000..8c005856d7 --- /dev/null +++ b/src/main/java/com/openclassrooms/tourguide/TourGuideModule.java @@ -0,0 +1,28 @@ +package com.openclassrooms.tourguide; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import gpsUtil.GpsUtil; +import rewardCentral.RewardCentral; +import com.openclassrooms.tourguide.service.RewardsService; + +@Configuration +public class TourGuideModule { + + @Bean + public GpsUtil getGpsUtil() { + return new GpsUtil(); + } + + @Bean + public RewardsService getRewardsService() { + return new RewardsService(getGpsUtil(), getRewardCentral()); + } + + @Bean + public RewardCentral getRewardCentral() { + return new RewardCentral(); + } + +} diff --git a/src/main/java/com/openclassrooms/tourguide/TourguideApplication.java b/src/main/java/com/openclassrooms/tourguide/TourguideApplication.java new file mode 100644 index 0000000000..c349efdd67 --- /dev/null +++ b/src/main/java/com/openclassrooms/tourguide/TourguideApplication.java @@ -0,0 +1,13 @@ +package com.openclassrooms.tourguide; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TourguideApplication { + + public static void main(String[] args) { + SpringApplication.run(TourguideApplication.class, args); + } + +} diff --git a/src/main/java/com/openclassrooms/tourguide/dto/AttractionInfo.java b/src/main/java/com/openclassrooms/tourguide/dto/AttractionInfo.java new file mode 100644 index 0000000000..ab335fd755 --- /dev/null +++ b/src/main/java/com/openclassrooms/tourguide/dto/AttractionInfo.java @@ -0,0 +1,78 @@ +package com.openclassrooms.tourguide.dto; + +public class AttractionInfo { + private String name; + private double attractionLatitude; + private double attractionLongitude; + private double userLatitude; + private double userLongitude; + private double distance; + private int rewardPoints; + + public AttractionInfo(String name, double attractionLatitude, double attractionLongitude, + double userLatitude, double userLongitude, double distance, int rewardPoints) { + this.name = name; + this.attractionLatitude = attractionLatitude; + this.attractionLongitude = attractionLongitude; + this.userLatitude = userLatitude; + this.userLongitude = userLongitude; + this.distance = distance; + this.rewardPoints = rewardPoints; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getAttractionLatitude() { + return attractionLatitude; + } + + public void setAttractionLatitude(double attractionLatitude) { + this.attractionLatitude = attractionLatitude; + } + + public double getAttractionLongitude() { + return attractionLongitude; + } + + public void setAttractionLongitude(double attractionLongitude) { + this.attractionLongitude = attractionLongitude; + } + + public double getUserLatitude() { + return userLatitude; + } + + public void setUserLatitude(double userLatitude) { + this.userLatitude = userLatitude; + } + + public double getUserLongitude() { + return userLongitude; + } + + public void setUserLongitude(double userLongitude) { + this.userLongitude = userLongitude; + } + + public double getDistance() { + return distance; + } + + public void setDistance(double distance) { + this.distance = distance; + } + + public int getRewardPoints() { + return rewardPoints; + } + + public void setRewardPoints(int rewardPoints) { + this.rewardPoints = rewardPoints; + } +} diff --git a/src/main/java/com/openclassrooms/tourguide/helper/InternalTestHelper.java b/src/main/java/com/openclassrooms/tourguide/helper/InternalTestHelper.java new file mode 100644 index 0000000000..ff792961f9 --- /dev/null +++ b/src/main/java/com/openclassrooms/tourguide/helper/InternalTestHelper.java @@ -0,0 +1,15 @@ +package com.openclassrooms.tourguide.helper; + +public class InternalTestHelper { + + // Set this default up to 100,000 for testing + private static int internalUserNumber = 1000; + + public static void setInternalUserNumber(int internalUserNumber) { + InternalTestHelper.internalUserNumber = internalUserNumber; + } + + public static int getInternalUserNumber() { + return internalUserNumber; + } +} diff --git a/src/main/java/com/openclassrooms/tourguide/service/RewardsService.java b/src/main/java/com/openclassrooms/tourguide/service/RewardsService.java new file mode 100644 index 0000000000..9b23071149 --- /dev/null +++ b/src/main/java/com/openclassrooms/tourguide/service/RewardsService.java @@ -0,0 +1,163 @@ +package com.openclassrooms.tourguide.service; + +import java.util.ArrayList; +import java.util.List; + + +import org.springframework.stereotype.Service; +import gpsUtil.GpsUtil; +import gpsUtil.location.Attraction; +import gpsUtil.location.Location; +import gpsUtil.location.VisitedLocation; +import rewardCentral.RewardCentral; +import com.openclassrooms.tourguide.user.User; +import com.openclassrooms.tourguide.user.UserReward; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Service +public class RewardsService { + private static final double STATUTE_MILES_PER_NAUTICAL_MILE = 1.15077945; + + // proximity in miles + private int defaultProximityBuffer = 10; + private int proximityBuffer = defaultProximityBuffer; + private int attractionProximityRange = 200; + private final GpsUtil gpsUtil; + private final RewardCentral rewardsCentral; + + private static final int THREAD_POOL_SIZE = 50; + + public static int getThreadPoolSize() { + return THREAD_POOL_SIZE; + } + + public RewardsService(GpsUtil gpsUtil, RewardCentral rewardCentral) { + this.gpsUtil = gpsUtil; + this.rewardsCentral = rewardCentral; + } + + public void setProximityBuffer(int proximityBuffer) { + this.proximityBuffer = proximityBuffer; + } + + public void setDefaultProximityBuffer() { + proximityBuffer = defaultProximityBuffer; + } + + + // parcourir la liste des emplacements visités par l'utilisateur (userLocations) + // et des attractions, tout en modifiant la liste des récompenses de l'utilisateur (user.getUserRewards()) + // vous modifiez la liste user.getUserRewards() en ajoutant des éléments à l'intérieur de la boucle \ + + /* + public void calculateRewards(User user) { + // List userLocations = user.getVisitedLocations(); + List userLocations = new ArrayList<>(user.getVisitedLocations()); + List attractions = gpsUtil.getAttractions(); + + List newRewards = new ArrayList(); + for (VisitedLocation visitedLocation : userLocations) { + + for (Attraction attraction : attractions) { + //if (user.getUserRewards().stream().filter(r -> r.attraction.attractionName.equals(attraction.attractionName)).count() == 0) { + if (newRewards.stream().filter(r -> r.attraction.attractionName.equals(attraction.attractionName)).count() == 0) { + if (nearAttraction(visitedLocation, attraction)) { + // user.addUserReward(new UserReward(visitedLocation, attraction, getRewardPoints(attraction, user))); + newRewards.add(new UserReward(visitedLocation, attraction, getRewardPoints(attraction, user))); + } + } + } + } + user.setUserRewards(newRewards); + } + + + */ + public void calculateRewards(User user) { + List userLocations = new ArrayList<>(user.getVisitedLocations()); + List attractions = gpsUtil.getAttractions(); + + List newRewards = new ArrayList<>(); + + userLocations.stream() + // Applique une fonction à chaque élément du flux (chaque VisitedLocation), produisant un flux interne + // pour chaque VisitedLocation. Le flux interne est créé à partir des attractions + // qui sont proches de la VisitedLocation actuelle + .flatMap(visitedLocation -> + attractions.stream() + .filter(attraction -> nearAttraction(visitedLocation, attraction)) + .filter(attraction -> newRewards.stream() + // si aucun élément du flux newRewards correspond à l'attraction actuelle + .noneMatch(r -> r.attraction.attractionName.equals(attraction.attractionName))) + // Transforme chaque attraction filtrée en un objet UserReward + .map(attraction -> new UserReward(visitedLocation, attraction, getRewardPoints(attraction, user))) + ) + // Ajoute chaque UserReward résultant à la liste newRewards + .forEach(newRewards::add); + + user.setUserRewards(newRewards); + } + + + + // Un pool de 50 threads est créé. + // Ensuite, une tâche est soumise à ce pool pour chaque utilisateur dans la liste users. + // Ces tâches sont exécutées en parallèle, avec un maximum de 50 tâches simultanées + // à un moment donné, grâce au pool de threads + + public void calculateRewardsAsync(List users) { + // Créez un ExecutorService avec le nombre souhaité de threads + ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE); + + // Créez un CountDownLatch avec un compte égal au nombre d'utilisateurs + CountDownLatch countDownLatch = new CountDownLatch(users.size()); + + // Soumettez des tâches pour chaque utilisateur + for (User user : users) { + executorService.submit(() -> { + // System.out.println(user.getEmailAddress()+" performed by "+ Thread.currentThread().getName()); + calculateRewards(user); + countDownLatch.countDown(); // Diminuez le compte lorsque la tâche est terminée + }); + } + + // Attendez que toutes les tâches soient terminées (besoin du test) + try { + countDownLatch.await(); // Cela attend indéfiniment jusqu'à ce que toutes les tâches soient terminées + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // Arrêtez l'ExecutorService + executorService.shutdown(); + } + + public boolean isWithinAttractionProximity(Attraction attraction, Location location) { + return getDistance(attraction, location) > attractionProximityRange ? false : true; + } + + private boolean nearAttraction(VisitedLocation visitedLocation, Attraction attraction) { + return getDistance(attraction, visitedLocation.location) > proximityBuffer ? false : true; + } + + public int getRewardPoints(Attraction attraction, User user) { + return rewardsCentral.getAttractionRewardPoints(attraction.attractionId, user.getUserId()); + } + + public double getDistance(Location loc1, Location loc2) { + double lat1 = Math.toRadians(loc1.latitude); + double lon1 = Math.toRadians(loc1.longitude); + double lat2 = Math.toRadians(loc2.latitude); + double lon2 = Math.toRadians(loc2.longitude); + + double angle = Math.acos(Math.sin(lat1) * Math.sin(lat2) + + Math.cos(lat1) * Math.cos(lat2) * Math.cos(lon1 - lon2)); + + double nauticalMiles = 60 * Math.toDegrees(angle); + double statuteMiles = STATUTE_MILES_PER_NAUTICAL_MILE * nauticalMiles; + return statuteMiles; + } + +} diff --git a/src/main/java/com/openclassrooms/tourguide/service/TourGuideService.java b/src/main/java/com/openclassrooms/tourguide/service/TourGuideService.java new file mode 100644 index 0000000000..50089b2565 --- /dev/null +++ b/src/main/java/com/openclassrooms/tourguide/service/TourGuideService.java @@ -0,0 +1,256 @@ +package com.openclassrooms.tourguide.service; + +import com.openclassrooms.tourguide.dto.AttractionInfo; +import com.openclassrooms.tourguide.helper.InternalTestHelper; +import com.openclassrooms.tourguide.tracker.Tracker; +import com.openclassrooms.tourguide.user.User; +import com.openclassrooms.tourguide.user.UserReward; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import gpsUtil.GpsUtil; +import gpsUtil.location.Attraction; +import gpsUtil.location.Location; +import gpsUtil.location.VisitedLocation; +import tripPricer.Provider; +import tripPricer.TripPricer; +import rewardCentral.RewardCentral; + + +@Service +public class TourGuideService { + private Logger logger = LoggerFactory.getLogger(TourGuideService.class); + private final GpsUtil gpsUtil; + private final RewardsService rewardsService; + private final TripPricer tripPricer = new TripPricer(); + public final Tracker tracker; + boolean testMode = true; + + public TourGuideService(GpsUtil gpsUtil, RewardsService rewardsService) { + this.gpsUtil = gpsUtil; + this.rewardsService = rewardsService; + + Locale.setDefault(Locale.US); + + if (testMode) { + logger.info("TestMode enabled"); + logger.debug("Initializing users"); + initializeInternalUsers(); + logger.debug("Finished initializing users"); + } + tracker = new Tracker(this); + addShutDownHook(); + } + + public List getUserRewards(User user) { + return user.getUserRewards(); + } + + public VisitedLocation getUserLocation(User user) { + VisitedLocation visitedLocation = (user.getVisitedLocations().size() > 0) ? user.getLastVisitedLocation() + : trackUserLocation(user); + return visitedLocation; + } + + public User getUser(String userName) { + return internalUserMap.get(userName); + } + + public List getAllUsers() { + return internalUserMap.values().stream().collect(Collectors.toList()); + } + + public void addUser(User user) { + if (!internalUserMap.containsKey(user.getUserName())) { + internalUserMap.put(user.getUserName(), user); + } + } + + /*effectue des calculs basés sur les points de récompense de + l'utilisateur, puis appelle un service externe (tripPricer) pour obtenir + des offres de voyage en fonction des préférences de l'utilisateur + Les offres sont ensuite associées à l'utilisateur et renvoyées */ + + + + public List getTripDeals(User user) { + int cumulatativeRewardPoints = user.getUserRewards().stream().mapToInt(i -> i.getRewardPoints()).sum(); + List providers = tripPricer.getPrice(tripPricerApiKey, user.getUserId(), + user.getUserPreferences().getNumberOfAdults(), user.getUserPreferences().getNumberOfChildren(), + user.getUserPreferences().getTripDuration(), cumulatativeRewardPoints); + user.setTripDeals(providers); + return providers; + } + + public VisitedLocation trackUserLocation(User user) { + VisitedLocation visitedLocation = gpsUtil.getUserLocation(user.getUserId()); + user.addToVisitedLocations(visitedLocation); + rewardsService.calculateRewards(user); + return visitedLocation; + } + + + public void trackUsersLocationAsync(List users) { + // Créez un ExecutorService avec le nombre souhaité de threads + ExecutorService executorService = Executors.newFixedThreadPool(RewardsService.getThreadPoolSize()); + + // Créez un CountDownLatch avec un compte égal au nombre d'utilisateurs + CountDownLatch countDownLatch = new CountDownLatch(users.size()); + + // Soumettez des tâches pour chaque utilisateur + for (User user : users) { + executorService.submit(() -> { + trackUserLocation(user); + countDownLatch.countDown(); // Diminuez le compte lorsque la tâche est terminée + }); + } + + // Attendez que toutes les tâches soient terminées + try { + countDownLatch.await(); // Cela attend indéfiniment jusqu'à ce que toutes les tâches soient terminées (besoin du test) + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // Arrêtez l'ExecutorService + executorService.shutdown(); + } + + + /* + public List getNearByAttractions(VisitedLocation visitedLocation) { + List nearbyAttractions = new ArrayList<>(); + for (Attraction attraction : gpsUtil.getAttractions()) { + // Vérifie si l'attraction est dans la proximité de la localisation visitée par l'utilisateur + if (rewardsService.isWithinAttractionProximity(attraction, visitedLocation.location)) { + nearbyAttractions.add(attraction); + } + } + + return nearbyAttractions; + } + */ + + public List getNearByAttractions(VisitedLocation visitedLocation, User user) { + List allAttractions = gpsUtil.getAttractions(); + // Trier la liste allAttractions en fonction de la distance entre chaque attraction et la dernière localisation de l'utilisateur + allAttractions.sort( + Comparator.comparingDouble(attraction -> rewardsService.getDistance(attraction, visitedLocation.location))); + + // Obtenir les 5 attractions les plus proches + List nearbyAttractions = allAttractions.stream().limit(5).collect(Collectors.toList()); + + // Créer une liste d'objets AttractionInfo pour contenir les informations demandées + List result = new ArrayList<>(); + + // Remplir la liste avec les informations de chaque attraction + for (Attraction attraction : nearbyAttractions) { + double distance = rewardsService.getDistance(attraction, visitedLocation.location); + + int rewardPoints = rewardsService.getRewardPoints(attraction, user); + + AttractionInfo attractionInfo = new AttractionInfo( + attraction.attractionName, + attraction.latitude, + attraction.longitude, + visitedLocation.location.latitude, + visitedLocation.location.longitude, + distance, + rewardPoints + ); + + result.add(attractionInfo); + } + + return result; + } + + /* +public List getNearByAttractions(VisitedLocation visitedLocation) { + List allAttractions = gpsUtil.getAttractions(); + + // Trier la liste allAttractions en fonction de la distance entre chaque attraction et la dernière localisation de l'utilisateur + + allAttractions.sort( + Comparator.comparingDouble(attraction -> rewardsService.getDistance(attraction, visitedLocation.location))); + + // Obtenir les 5 attractions les plus proches + + List nearbyAttractions = allAttractions.stream().limit(5).collect(Collectors.toList()); + + return nearbyAttractions; +} + +*/ + + private void addShutDownHook() { + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() { + tracker.stopTracking(); + } + }); + } + + /********************************************************************************** + * + * Methods Below: For Internal Testing + * + **********************************************************************************/ + private static final String tripPricerApiKey = "test-server-api-key"; + // Database connection will be used for external users, but for testing purposes + // internal users are provided and stored in memory + private final Map internalUserMap = new HashMap<>(); + + private void initializeInternalUsers() { + IntStream.range(0, InternalTestHelper.getInternalUserNumber()).forEach(i -> { + String userName = "internalUser" + i; + String phone = "000"; + String email = userName + "@tourGuide.com"; + User user = new User(UUID.randomUUID(), userName, phone, email); + generateUserLocationHistory(user); + + internalUserMap.put(userName, user); + }); + logger.debug("Created " + InternalTestHelper.getInternalUserNumber() + " internal test users."); + } + + private void generateUserLocationHistory(User user) { + IntStream.range(0, 3).forEach(i -> { + user.addToVisitedLocations(new VisitedLocation(user.getUserId(), + new Location(generateRandomLatitude(), generateRandomLongitude()), getRandomTime())); + }); + } + + private double generateRandomLongitude() { + double leftLimit = -180; + double rightLimit = 180; + return leftLimit + new Random().nextDouble() * (rightLimit - leftLimit); + } + + private double generateRandomLatitude() { + double leftLimit = -85.05112878; + double rightLimit = 85.05112878; + return leftLimit + new Random().nextDouble() * (rightLimit - leftLimit); + } + + private Date getRandomTime() { + LocalDateTime localDateTime = LocalDateTime.now().minusDays(new Random().nextInt(30)); + return Date.from(localDateTime.toInstant(ZoneOffset.UTC)); + } +} diff --git a/src/main/java/com/openclassrooms/tourguide/tracker/Tracker.java b/src/main/java/com/openclassrooms/tourguide/tracker/Tracker.java new file mode 100644 index 0000000000..5fea6b96f5 --- /dev/null +++ b/src/main/java/com/openclassrooms/tourguide/tracker/Tracker.java @@ -0,0 +1,63 @@ +package com.openclassrooms.tourguide.tracker; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.lang3.time.StopWatch; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.openclassrooms.tourguide.service.RewardsService; +import com.openclassrooms.tourguide.service.TourGuideService; +import com.openclassrooms.tourguide.user.User; + +public class Tracker extends Thread { + private Logger logger = LoggerFactory.getLogger(Tracker.class); + private static final long trackingPollingInterval = TimeUnit.MINUTES.toSeconds(5); + private final ExecutorService executorService = Executors.newSingleThreadExecutor(); + private final TourGuideService tourGuideService; + private boolean stop = false; + + public Tracker(TourGuideService tourGuideService) { + this.tourGuideService = tourGuideService; + + executorService.submit(this); + } + + /** + * Assures to shut down the Tracker thread + */ + public void stopTracking() { + stop = true; + executorService.shutdownNow(); + } + + @Override + public void run() { + StopWatch stopWatch = new StopWatch(); + while (true) { + if (Thread.currentThread().isInterrupted() || stop) { + logger.debug("Tracker stopping"); + break; + } + + List users = tourGuideService.getAllUsers(); + logger.debug("Begin Tracker. Tracking " + users.size() + " users."); + stopWatch.start(); + users.forEach(u -> tourGuideService.trackUserLocation(u)); + stopWatch.stop(); + logger.debug("Tracker Time Elapsed: " + TimeUnit.MILLISECONDS.toSeconds(stopWatch.getTime()) + " seconds."); + stopWatch.reset(); + try { + logger.debug("Tracker sleeping"); + TimeUnit.SECONDS.sleep(trackingPollingInterval); + } catch (InterruptedException e) { + break; + } + } + + } +} diff --git a/src/main/java/com/openclassrooms/tourguide/user/User.java b/src/main/java/com/openclassrooms/tourguide/user/User.java new file mode 100644 index 0000000000..10e0bf4359 --- /dev/null +++ b/src/main/java/com/openclassrooms/tourguide/user/User.java @@ -0,0 +1,111 @@ +package com.openclassrooms.tourguide.user; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import gpsUtil.location.VisitedLocation; +import tripPricer.Provider; + +public class User { + private final UUID userId; + private final String userName; + private String phoneNumber; + private String emailAddress; + private Date latestLocationTimestamp; + private List visitedLocations = new ArrayList<>(); + private List userRewards = new ArrayList<>(); + private UserPreferences userPreferences = new UserPreferences(); + private List tripDeals = new ArrayList<>(); + public User(UUID userId, String userName, String phoneNumber, String emailAddress) { + this.userId = userId; + this.userName = userName; + this.phoneNumber = phoneNumber; + this.emailAddress = emailAddress; + } + + public UUID getUserId() { + return userId; + } + + public String getUserName() { + return userName; + } + + public void setPhoneNumber(String phoneNumber) { + this.phoneNumber = phoneNumber; + } + + public String getPhoneNumber() { + return phoneNumber; + } + + public void setEmailAddress(String emailAddress) { + this.emailAddress = emailAddress; + } + + public String getEmailAddress() { + return emailAddress; + } + + public void setLatestLocationTimestamp(Date latestLocationTimestamp) { + this.latestLocationTimestamp = latestLocationTimestamp; + } + + public Date getLatestLocationTimestamp() { + return latestLocationTimestamp; + } + + public void addToVisitedLocations(VisitedLocation visitedLocation) { + visitedLocations.add(visitedLocation); + } + + public List getVisitedLocations() { + return visitedLocations; + } + + public void clearVisitedLocations() { + visitedLocations.clear(); + } + + // un UserReward avec la même attraction n'est pas ajouté plusieurs fois à la liste + + // exclure tous les éléments qui ont le même nom d'attraction que l'objet userReward actuel + + public void addUserReward(UserReward userReward) { + if(userRewards.stream().filter(r -> !r.attraction.attractionName.equals(userReward.attraction)).count() == 0) { + userRewards.add(userReward); + } + } + + + public void setUserRewards(List userRewards) { + this.userRewards = userRewards; + } + + public List getUserRewards() { + return userRewards; + } + + public UserPreferences getUserPreferences() { + return userPreferences; + } + + public void setUserPreferences(UserPreferences userPreferences) { + this.userPreferences = userPreferences; + } + + public VisitedLocation getLastVisitedLocation() { + return visitedLocations.get(visitedLocations.size() - 1); + } + + public void setTripDeals(List tripDeals) { + this.tripDeals = tripDeals; + } + + public List getTripDeals() { + return tripDeals; + } + +} diff --git a/src/main/java/com/openclassrooms/tourguide/user/UserPreferences.java b/src/main/java/com/openclassrooms/tourguide/user/UserPreferences.java new file mode 100644 index 0000000000..98e712ffae --- /dev/null +++ b/src/main/java/com/openclassrooms/tourguide/user/UserPreferences.java @@ -0,0 +1,54 @@ +package com.openclassrooms.tourguide.user; + +public class UserPreferences { + + private int attractionProximity = Integer.MAX_VALUE; + private int tripDuration = 1; + private int ticketQuantity = 1; + private int numberOfAdults = 1; + private int numberOfChildren = 0; + + public UserPreferences() { + } + + public void setAttractionProximity(int attractionProximity) { + this.attractionProximity = attractionProximity; + } + + public int getAttractionProximity() { + return attractionProximity; + } + + public int getTripDuration() { + return tripDuration; + } + + public void setTripDuration(int tripDuration) { + this.tripDuration = tripDuration; + } + + public int getTicketQuantity() { + return ticketQuantity; + } + + public void setTicketQuantity(int ticketQuantity) { + this.ticketQuantity = ticketQuantity; + } + + public int getNumberOfAdults() { + return numberOfAdults; + } + + public void setNumberOfAdults(int numberOfAdults) { + this.numberOfAdults = numberOfAdults; + } + + public int getNumberOfChildren() { + return numberOfChildren; + } + + public void setNumberOfChildren(int numberOfChildren) { + this.numberOfChildren = numberOfChildren; + } + +} diff --git a/src/main/java/com/openclassrooms/tourguide/user/UserReward.java b/src/main/java/com/openclassrooms/tourguide/user/UserReward.java new file mode 100644 index 0000000000..263e392045 --- /dev/null +++ b/src/main/java/com/openclassrooms/tourguide/user/UserReward.java @@ -0,0 +1,30 @@ +package com.openclassrooms.tourguide.user; + +import gpsUtil.location.Attraction; +import gpsUtil.location.VisitedLocation; + +public class UserReward { + + public final VisitedLocation visitedLocation; + public final Attraction attraction; + private int rewardPoints; + public UserReward(VisitedLocation visitedLocation, Attraction attraction, int rewardPoints) { + this.visitedLocation = visitedLocation; + this.attraction = attraction; + this.rewardPoints = rewardPoints; + } + + public UserReward(VisitedLocation visitedLocation, Attraction attraction) { + this.visitedLocation = visitedLocation; + this.attraction = attraction; + } + + public void setRewardPoints(int rewardPoints) { + this.rewardPoints = rewardPoints; + } + + public int getRewardPoints() { + return rewardPoints; + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000000..043555abc7 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1 @@ +logging.level.com.openclassrooms.tourguide=DEBUG \ No newline at end of file diff --git a/src/test/java/com/nnk/springboot/TourGuideeApplicationTests.java b/src/test/java/com/nnk/springboot/TourGuideeApplicationTests.java new file mode 100644 index 0000000000..b2bd790f46 --- /dev/null +++ b/src/test/java/com/nnk/springboot/TourGuideeApplicationTests.java @@ -0,0 +1,13 @@ +package com.nnk.springboot; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class TourGuideeApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/src/test/java/com/openclassrooms/tourguide/TestPerformance.java b/src/test/java/com/openclassrooms/tourguide/TestPerformance.java new file mode 100644 index 0000000000..86de9433d2 --- /dev/null +++ b/src/test/java/com/openclassrooms/tourguide/TestPerformance.java @@ -0,0 +1,118 @@ +package com.openclassrooms.tourguide; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.lang3.time.StopWatch; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import gpsUtil.GpsUtil; +import gpsUtil.location.Attraction; +import gpsUtil.location.VisitedLocation; +import rewardCentral.RewardCentral; +import com.openclassrooms.tourguide.helper.InternalTestHelper; +import com.openclassrooms.tourguide.service.RewardsService; +import com.openclassrooms.tourguide.service.TourGuideService; +import com.openclassrooms.tourguide.user.User; + +public class TestPerformance { + + /* + * A note on performance improvements: + * + * The number of users generated for the high volume tests can be easily + * adjusted via this method: + * + * InternalTestHelper.setInternalUserNumber(100000); + * + * + * These tests can be modified to suit new solutions, just as long as the + * performance metrics at the end of the tests remains consistent. + * + * These are performance metrics that we are trying to hit: + * + * highVolumeTrackLocation: 100,000 users within 15 minutes: + * assertTrue(TimeUnit.MINUTES.toSeconds(15) >= + * TimeUnit.MILLISECONDS.toSeconds(stopWatch.getTime())); + * + * highVolumeGetRewards: 100,000 users within 20 minutes: + * assertTrue(TimeUnit.MINUTES.toSeconds(20) >= + * TimeUnit.MILLISECONDS.toSeconds(stopWatch.getTime())); + */ + + //@Disabled + @Test + public void highVolumeTrackLocation() { + GpsUtil gpsUtil = new GpsUtil(); + RewardsService rewardsService = new RewardsService(gpsUtil, new RewardCentral()); + // Users should be incremented up to 100,000, and test finishes within 15 + // minutes + //InternalTestHelper.setInternalUserNumber(100); + InternalTestHelper.setInternalUserNumber(InternalTestHelper.getInternalUserNumber()); + + TourGuideService tourGuideService = new TourGuideService(gpsUtil, rewardsService); + + List allUsers = new ArrayList<>(); + allUsers = tourGuideService.getAllUsers(); + + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + + /* + for (User user : allUsers) { + tourGuideService.trackUserLocation(user); + } + */ + + tourGuideService.trackUsersLocationAsync(allUsers); + + stopWatch.stop(); + tourGuideService.tracker.stopTracking(); + + System.out.println("highVolumeTrackLocation: Time Elapsed: " + + TimeUnit.MILLISECONDS.toSeconds(stopWatch.getTime()) + " seconds."); + assertTrue(TimeUnit.MINUTES.toSeconds(15) >= TimeUnit.MILLISECONDS.toSeconds(stopWatch.getTime())); + } + + +// @Disabled + @Test + public void highVolumeGetRewards() { + GpsUtil gpsUtil = new GpsUtil(); + RewardsService rewardsService = new RewardsService(gpsUtil, new RewardCentral()); + + // Users should be incremented up to 100,000, and test finishes within 20 + // minutes + //InternalTestHelper.setInternalUserNumber(100); + InternalTestHelper.setInternalUserNumber(InternalTestHelper.getInternalUserNumber()); + + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + TourGuideService tourGuideService = new TourGuideService(gpsUtil, rewardsService); + + Attraction attraction = gpsUtil.getAttractions().get(0); + List allUsers = new ArrayList<>(); + allUsers = tourGuideService.getAllUsers(); + allUsers.forEach(u -> u.addToVisitedLocations(new VisitedLocation(u.getUserId(), attraction, new Date()))); + + // allUsers.forEach(u -> rewardsService.calculateRewards(u)); + + rewardsService.calculateRewardsAsync(allUsers); + + for (User user : allUsers) { + assertTrue(user.getUserRewards().size() > 0); + } + stopWatch.stop(); + tourGuideService.tracker.stopTracking(); + + System.out.println("highVolumeGetRewards: Time Elapsed: " + TimeUnit.MILLISECONDS.toSeconds(stopWatch.getTime()) + + " seconds."); + assertTrue(TimeUnit.MINUTES.toSeconds(20) >= TimeUnit.MILLISECONDS.toSeconds(stopWatch.getTime())); + } + +} diff --git a/src/test/java/com/openclassrooms/tourguide/TestRewardsService.java b/src/test/java/com/openclassrooms/tourguide/TestRewardsService.java new file mode 100644 index 0000000000..538df7f6cd --- /dev/null +++ b/src/test/java/com/openclassrooms/tourguide/TestRewardsService.java @@ -0,0 +1,87 @@ +package com.openclassrooms.tourguide; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import gpsUtil.GpsUtil; +import gpsUtil.location.Attraction; +import gpsUtil.location.VisitedLocation; +import rewardCentral.RewardCentral; +import com.openclassrooms.tourguide.helper.InternalTestHelper; +import com.openclassrooms.tourguide.service.RewardsService; +import com.openclassrooms.tourguide.service.TourGuideService; +import com.openclassrooms.tourguide.user.User; +import com.openclassrooms.tourguide.user.UserReward; +//import com.openclassrooms.tourguide.user.User; + +public class TestRewardsService { + + @Test + public void userGetRewards() { + GpsUtil gpsUtil = new GpsUtil(); + RewardsService rewardsService = new RewardsService(gpsUtil, new RewardCentral()); + + InternalTestHelper.setInternalUserNumber(0); + TourGuideService tourGuideService = new TourGuideService(gpsUtil, rewardsService); + + User user = new User(UUID.randomUUID(), "jon", "000", "jon@tourGuide.com"); + Attraction attraction = gpsUtil.getAttractions().get(0); + user.addToVisitedLocations(new VisitedLocation(user.getUserId(), attraction, new Date())); + tourGuideService.trackUserLocation(user); + List userRewards = user.getUserRewards(); + tourGuideService.tracker.stopTracking(); + assertTrue(userRewards.size() == 1); + } + + @Test + public void isWithinAttractionProximity() { + GpsUtil gpsUtil = new GpsUtil(); + RewardsService rewardsService = new RewardsService(gpsUtil, new RewardCentral()); + Attraction attraction = gpsUtil.getAttractions().get(0); + assertTrue(rewardsService.isWithinAttractionProximity(attraction, attraction)); + } + + + // attribue correctement des récompenses à un utilisateur pour sa proximité avec toutes les attractions + + // @Disabled // Needs fixed - can throw ConcurrentModificationException + // ConcurrentModification + + @Test + public void nearAllAttractions() { + GpsUtil gpsUtil = new GpsUtil(); + + // RewardCentral pour la gestion des récompenses + RewardsService rewardsService = new RewardsService(gpsUtil, new RewardCentral()); + + // Ignorer les considérations de proximité lors du calcul des récompenses pour un utilisateur + rewardsService.setProximityBuffer(Integer.MAX_VALUE); + + // Configurer l'environnement de test avec un seul utilisateur interne + InternalTestHelper.setInternalUserNumber(1); + + TourGuideService tourGuideService = new TourGuideService(gpsUtil, rewardsService); + + // Calcul des récompenses pour le premier utilisateur obtenu + + rewardsService.calculateRewards(tourGuideService.getAllUsers().get(0)); + + // Récupère les récompenses associées au premier utilisateur obtenu + // à partir du service tourGuideService et les stocke dans la liste userRewards + + List userRewards = tourGuideService.getUserRewards(tourGuideService.getAllUsers().get(0)); + + tourGuideService.tracker.stopTracking(); + + // Chaque attraction du système a une récompense associée pour l'utilisateur spécifié + assertEquals(gpsUtil.getAttractions().size(), userRewards.size()); + } +} diff --git a/src/test/java/com/openclassrooms/tourguide/TestTourGuideService.java b/src/test/java/com/openclassrooms/tourguide/TestTourGuideService.java new file mode 100644 index 0000000000..b2a58def20 --- /dev/null +++ b/src/test/java/com/openclassrooms/tourguide/TestTourGuideService.java @@ -0,0 +1,137 @@ +package com.openclassrooms.tourguide; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import gpsUtil.GpsUtil; +import gpsUtil.location.Attraction; +import gpsUtil.location.Location; +import gpsUtil.location.VisitedLocation; +import rewardCentral.RewardCentral; + +import com.openclassrooms.tourguide.dto.AttractionInfo; +import com.openclassrooms.tourguide.helper.InternalTestHelper; +import com.openclassrooms.tourguide.service.RewardsService; +import com.openclassrooms.tourguide.service.TourGuideService; +import com.openclassrooms.tourguide.user.User; +import tripPricer.Provider; +import com.openclassrooms.tourguide.user.UserReward; + +import gpsUtil.location.Attraction; +import gpsUtil.location.VisitedLocation; + +public class TestTourGuideService { + + @Test + public void getUserLocation() { + GpsUtil gpsUtil = new GpsUtil(); + RewardsService rewardsService = new RewardsService(gpsUtil, new RewardCentral()); + InternalTestHelper.setInternalUserNumber(0); + TourGuideService tourGuideService = new TourGuideService(gpsUtil, rewardsService); + + User user = new User(UUID.randomUUID(), "jon", "000", "jon@tourGuide.com"); + VisitedLocation visitedLocation = tourGuideService.trackUserLocation(user); + tourGuideService.tracker.stopTracking(); + assertTrue(visitedLocation.userId.equals(user.getUserId())); + } + + @Test + public void addUser() { + GpsUtil gpsUtil = new GpsUtil(); + RewardsService rewardsService = new RewardsService(gpsUtil, new RewardCentral()); + InternalTestHelper.setInternalUserNumber(0); + TourGuideService tourGuideService = new TourGuideService(gpsUtil, rewardsService); + + User user = new User(UUID.randomUUID(), "jon", "000", "jon@tourGuide.com"); + User user2 = new User(UUID.randomUUID(), "jon2", "000", "jon2@tourGuide.com"); + + tourGuideService.addUser(user); + tourGuideService.addUser(user2); + + User retrivedUser = tourGuideService.getUser(user.getUserName()); + User retrivedUser2 = tourGuideService.getUser(user2.getUserName()); + + tourGuideService.tracker.stopTracking(); + + assertEquals(user, retrivedUser); + assertEquals(user2, retrivedUser2); + } + + @Test + public void getAllUsers() { + GpsUtil gpsUtil = new GpsUtil(); + RewardsService rewardsService = new RewardsService(gpsUtil, new RewardCentral()); + InternalTestHelper.setInternalUserNumber(0); + TourGuideService tourGuideService = new TourGuideService(gpsUtil, rewardsService); + + User user = new User(UUID.randomUUID(), "jon", "000", "jon@tourGuide.com"); + User user2 = new User(UUID.randomUUID(), "jon2", "000", "jon2@tourGuide.com"); + + tourGuideService.addUser(user); + tourGuideService.addUser(user2); + + List allUsers = tourGuideService.getAllUsers(); + + tourGuideService.tracker.stopTracking(); + + assertTrue(allUsers.contains(user)); + assertTrue(allUsers.contains(user2)); + } + + @Test + public void trackUser() { + GpsUtil gpsUtil = new GpsUtil(); + RewardsService rewardsService = new RewardsService(gpsUtil, new RewardCentral()); + InternalTestHelper.setInternalUserNumber(0); + TourGuideService tourGuideService = new TourGuideService(gpsUtil, rewardsService); + + User user = new User(UUID.randomUUID(), "jon", "000", "jon@tourGuide.com"); + VisitedLocation visitedLocation = tourGuideService.trackUserLocation(user); + + tourGuideService.tracker.stopTracking(); + + assertEquals(user.getUserId(), visitedLocation.userId); + } + + // @Disabled // Not yet implemented + + // les 5 attractions les plus proches de la dernière localisation de + // l'utilisateur + + @Test + public void getNearbyAttractions() { + GpsUtil gpsUtil = new GpsUtil(); + RewardsService rewardsService = new RewardsService(gpsUtil, new RewardCentral()); + InternalTestHelper.setInternalUserNumber(1); + TourGuideService tourGuideService = new TourGuideService(gpsUtil, rewardsService); + + User user = new User(UUID.randomUUID(), "jon", "000", "jon@tourGuide.com"); + VisitedLocation visitedLocation = tourGuideService.trackUserLocation(user); + + List attractions = tourGuideService.getNearByAttractions(visitedLocation, user); + + tourGuideService.tracker.stopTracking(); + + assertEquals(5, attractions.size()); + } + + + @Test + public void getTripDeals() { + GpsUtil gpsUtil = new GpsUtil(); + RewardsService rewardsService = new RewardsService(gpsUtil, new RewardCentral()); + InternalTestHelper.setInternalUserNumber(0); + TourGuideService tourGuideService = new TourGuideService(gpsUtil, rewardsService); + User user = new User(UUID.randomUUID(), "jon", "000", "jon@tourGuide.com"); + List providers = tourGuideService.getTripDeals(user); + tourGuideService.tracker.stopTracking(); + assertEquals(5, providers.size()); + } +} diff --git a/src/test/java/com/openclassrooms/tourguide/TourguideApplicationTests.java b/src/test/java/com/openclassrooms/tourguide/TourguideApplicationTests.java new file mode 100644 index 0000000000..3838478709 --- /dev/null +++ b/src/test/java/com/openclassrooms/tourguide/TourguideApplicationTests.java @@ -0,0 +1,13 @@ +package com.openclassrooms.tourguide; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class TourguideApplicationTests { + + @Test + void contextLoads() { + } + +}