| 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 | }}} |