| | 299 | |
| | 300 | === Испраќа известување до корисници на услуги |
| | 301 | За имплементација на оваа функционалност го користиме сервисот за испраќање пораки наведен погоре и дополнително имплементиран Listener и Event. Spring претставува event-driven архитектура, односно им овозможува на компононетите да комуницираат преку пропагација на настани во улога на publisher и subscriber. Овој механизам е типичен пример за примена на шаблонот Observer. И покарј тоа што голем дел од предефинираните компоненти во Spring комуницираат на овој начин, направивме custom имплементација на овој шаблон за ова сценарио. |
| | 302 | {{{#!java |
| | 303 | public abstract class EmailEvent extends ApplicationEvent { |
| | 304 | protected EventType eventType; |
| | 305 | protected User user; |
| | 306 | protected String subject; |
| | 307 | protected String message; |
| | 308 | |
| | 309 | public EmailEvent(User user) { |
| | 310 | super(user); |
| | 311 | this.user = user; |
| | 312 | } |
| | 313 | |
| | 314 | public User getUser() { |
| | 315 | return user; |
| | 316 | } |
| | 317 | |
| | 318 | public void setUser(User user) { |
| | 319 | this.user = user; |
| | 320 | } |
| | 321 | |
| | 322 | public EventType getEventType() { |
| | 323 | return eventType; |
| | 324 | } |
| | 325 | |
| | 326 | public void setEventType(EventType eventType) { |
| | 327 | this.eventType = eventType; |
| | 328 | } |
| | 329 | |
| | 330 | public String getSubject() { |
| | 331 | return subject; |
| | 332 | } |
| | 333 | |
| | 334 | public void setSubject(String subject) { |
| | 335 | this.subject = subject; |
| | 336 | } |
| | 337 | |
| | 338 | public String getMessage() { |
| | 339 | return message; |
| | 340 | } |
| | 341 | |
| | 342 | public void setMessage(String message) { |
| | 343 | this.message = message; |
| | 344 | } |
| | 345 | } |
| | 346 | }}} |
| | 347 | Имено, при регистрација на нов корисник, нова резервација, одобрување на профил или фирма се исплаува соодветен настан, објект кој наследува од EmailEvent и во конструкторот се справува со subject и message зависнот од типот. Овој настан го пречекува новодефнираниот listener и соодветно се извршува логиката за испраќање на порака. |
| | 348 | {{{#!java |
| | 349 | public class OnRegistrationSuccessEvent extends EmailEvent { |
| | 350 | private static final long serialVersionUID = 1L; |
| | 351 | private User user; |
| | 352 | |
| | 353 | public OnRegistrationSuccessEvent(User user) { |
| | 354 | super(user); |
| | 355 | this.eventType = EventType.REGISTRATION; |
| | 356 | this.subject = "TourMate - Успешна регистрација"; |
| | 357 | this.message = "Драг кориснику,\n\nВе известуваме дека Вашата регистрација на апликацијата TourMate е успешна. За да можете да го користите профилот, потребно е истиот да е одобрен од страна на администраторот за што ќе добиете дополнителна потврда на оваа адреса.\n\n\nСо почит,\nTourMate"; |
| | 358 | } |
| | 359 | } |
| | 360 | }}} |
| | 361 | {{{#!java |
| | 362 | @Override |
| | 363 | @Transactional |
| | 364 | public void approveUserProfile(User u) { |
| | 365 | u.setEnabled(true); |
| | 366 | em.persist(u); |
| | 367 | eventPublisher.publishEvent(new OnProfileEnabledEvent(u)); |
| | 368 | } |
| | 369 | }}} |
| | 370 | {{{#!java |
| | 371 | @Component |
| | 372 | public class RegistrationEmailListener implements ApplicationListener<EmailEvent> { |
| | 373 | |
| | 374 | @Autowired |
| | 375 | private Environment environment; |
| | 376 | @Autowired |
| | 377 | private MailingService mailingService; |
| | 378 | @Override |
| | 379 | public void onApplicationEvent(EmailEvent event) { |
| | 380 | mailingService.sendMail(event.getUser().getEmail(), event.getSubject(), event.getMessage()); |
| | 381 | } |
| | 382 | } |
| | 383 | }}} |
| | 384 | === Листа сместувања |
| | 385 | Мала промена има во приказот на резултатите од ова корисничко сценарио. Врз основа на некои основни статистики, хотелите кои во дадениот период се наоѓаат во топ 5 според бројот на резервации се означени со беџ „Најпопуларен“ и позадина во друга боја, додека пак оние кои имаат малку или немаат воопшто резервација, добиваат случајно една од ознаките „специјална цена“, „последна соба“ или „достапно само денес“ како извесен кавзи-маркетиншки трик. |
| | 386 | Промена за ова е направена само во сервисот кој го враќа DTO-от за хотелот, каде се додадени проверки за тоа во која категорија припаѓа хотелот. |
| | 387 | {{{#!java |
| | 388 | @Override |
| | 389 | public List<HotelDto> getRoomsAvailibilityByDateAndLocation(String hotelLocation, Date dateFrom, Date dateTo, int numberOfBeds, Boolean flexible) { |
| | 390 | long numberOfNights = Duration.between(dateFrom.toInstant(), dateTo.toInstant()).toDays(); |
| | 391 | List<Hotels> hotels = getHotelsByLocation(hotelLocation); |
| | 392 | List<HotelRoomReservations> hotelRoomReservations = getReservationsInPeriod(hotelLocation, dateFrom, dateTo); |
| | 393 | List<Hotels> hotelsWithReservations = hotelRoomReservations.stream().map(x -> x.getHotelRoom().getHotel()).toList(); |
| | 394 | List<Hotels> mostReservedHotels = hotelRoomReservations.stream() |
| | 395 | .collect(Collectors.groupingBy(x -> x.getHotelRoom().getHotel(), Collectors.counting())).entrySet().stream() |
| | 396 | .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())) |
| | 397 | .limit(5) |
| | 398 | .map(Map.Entry::getKey) |
| | 399 | .toList(); |
| | 400 | List<Hotels> leastReservedHotels = hotelRoomReservations.stream() |
| | 401 | .collect(Collectors.groupingBy(x -> x.getHotelRoom().getHotel(), Collectors.counting())).entrySet().stream() |
| | 402 | .sorted(Map.Entry.comparingByValue()) |
| | 403 | .limit(5) |
| | 404 | .map(Map.Entry::getKey) |
| | 405 | .toList(); |
| | 406 | List<Hotels> hotelsWithoutReservations = hotels.stream().filter(x -> !hotelsWithReservations.contains(x)).toList(); |
| | 407 | List<Hotels> hotelsToBeMarketed = new ArrayList<>(); |
| | 408 | hotelsToBeMarketed.addAll(hotelsWithoutReservations); |
| | 409 | hotelsToBeMarketed.addAll(leastReservedHotels); |
| | 410 | List<HotelRoomAvailable> roomsAvailible = hotelDao.getRoomsAvailibilityByDateAndLocation(hotelLocation, dateFrom, dateTo, numberOfBeds, flexible); |
| | 411 | Map<Hotels, List<HotelRoomAvailable>> roomsByHotels = roomsAvailible.stream().collect(Collectors.groupingBy(x -> x.getHotelRoom().getHotel())); |
| | 412 | List<HotelDto> hotelsList = roomsByHotels.keySet().stream() |
| | 413 | .map(x -> new HotelDto( |
| | 414 | x.getHotelId(), |
| | 415 | x.getHotelName(), |
| | 416 | x.getHotelDescripiton(), |
| | 417 | x.getHotelLocation(), |
| | 418 | x.getHotelEDBS(), |
| | 419 | x.getParking(), |
| | 420 | x.getPetFriendly(), |
| | 421 | x.getInternetAvailable(), |
| | 422 | roomsByHotels.get(x).stream().mapToDouble(y -> y.getHotelRoom().getPrice()).min().getAsDouble() * numberOfNights, |
| | 423 | roomsByHotels.get(x), |
| | 424 | getReviewsForHotel(x.getHotelId()), |
| | 425 | getReviewsForHotel(x.getHotelId()).stream().mapToDouble(Reviews::getNumStar).average().orElse(0), |
| | 426 | getHotelImages(x.getHotelId()), |
| | 427 | mostReservedHotels.contains(x), |
| | 428 | hotelsToBeMarketed.contains(x) |
| | 429 | )).toList(); |
| | 430 | return hotelsList; |
| | 431 | } |
| | 432 | }}} |