de.steilerdev.myVerein.server.controller.admin.EventManagementController.java Source code

Java tutorial

Introduction

Here is the source code for de.steilerdev.myVerein.server.controller.admin.EventManagementController.java

Source

/**
 * Copyright (C) 2015 Frank Steiler <frank@steilerdev.de>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package de.steilerdev.myVerein.server.controller.admin;

import de.steilerdev.myVerein.server.model.*;
import de.steilerdev.myVerein.server.security.CurrentUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.validation.ConstraintViolationException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * This controller is processing all requests associated with the event management.
 */
@RestController
@RequestMapping("/api/admin/event")
public class EventManagementController {
    @Autowired
    EventRepository eventRepository;

    @Autowired
    DivisionRepository divisionRepository;

    private static Logger logger = LoggerFactory.getLogger(EventManagementController.class);

    /**
     * This function gathers all dates where an event takes place within a specific month and year. The function is invoked by GETting the URI /api/admin/event/month and specifying the month and year via the request parameters.
     * @param month The selected month.
     * @param year The selected year.
     * @return An HTTP response with a status code. If the function succeeds, a list of dates, during the month, that contain an event, is returned, otherwise an error code is returned.
     */
    @RequestMapping(value = "month", produces = "application/json", method = RequestMethod.GET)
    public ResponseEntity<List<LocalDate>> getEventDatesOfMonth(@RequestParam String month,
            @RequestParam String year, @CurrentUser User currentUser) {
        logger.trace("[" + currentUser + "] Gathering events of month " + month + " and year " + year);
        ArrayList<LocalDate> dates = new ArrayList<>();
        int monthInt, yearInt;
        try {
            monthInt = Integer.parseInt(month);
            yearInt = Integer.parseInt(year);
        } catch (NumberFormatException e) {
            logger.warn("[" + currentUser + "] Unable to parse month or year.");
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }

        LocalDateTime start = LocalDate.of(yearInt, monthInt, 1).atStartOfDay();
        LocalDateTime end = start.plusMonths(1);

        logger.debug("Getting all single day events...");
        dates.addAll(eventRepository.findAllByEndDateTimeBetweenAndMultiDate(start, end, false).parallelStream()
                .map(Event::getStartDate).collect(Collectors.toList()));
        logger.debug("All single day events retrieved, got " + dates.size() + " dates so far");

        logger.debug("Getting all multi day events...");
        //Collecting all multi date events, that either start or end within the selected month (which means that events that are spanning over several months are not collected)
        Stream.concat(eventRepository.findAllByStartDateTimeBetweenAndMultiDate(start, end, true).stream(), //All multi date events starting within the month
                eventRepository.findAllByEndDateTimeBetweenAndMultiDate(start, end, true).stream()) //All multi date events ending within the month
                .distinct() //Removing all duplicated events
                .parallel().forEach(event -> { //Creating a local date for each occupied date of the event
                    for (LocalDate date = event.getStartDate(); //Starting with the start date
                            !date.equals(event.getEndDateTime().toLocalDate()); //Until reaching the end date
                            date = date.plusDays(1)) //Adding a day within each iterations
                    {
                        dates.add(date);
                    }
                    dates.add(event.getEndDateTime().toLocalDate()); //Finally adding the last date
                });
        logger.debug("All multi day events gathered, got " + dates.size() + " dates so far");

        if (dates.isEmpty()) {
            logger.warn("[" + currentUser + "] Returning empty dates list of " + month + "/" + year);
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        } else {
            logger.debug("[" + currentUser + "] Returning dates list " + month + "/" + year);
            return new ResponseEntity<>(dates.stream().distinct().collect(Collectors.toList()), HttpStatus.OK); //Returning an optimized set of events
        }
    }

    /**
     * Returns all events, that are taking place on a specified date. The date parameter needs to be formatted according to the following pattern: YYYY/MM/DD. This function is invoked by GETting the URI /api/admin/event/date
     * @param date The selected date, correctly formatted (YYYY/MM/DD)
     * @return An HTTP response with a status code. If the function succeeds, a list of events is returned, otherwise an error code is returned.
     */
    @RequestMapping(value = "date", produces = "application/json", method = RequestMethod.GET)
    public ResponseEntity<List<Event>> getEventsOfDate(@RequestParam String date, @CurrentUser User currentUser) {
        logger.trace("[" + currentUser + "] Getting events of date " + date);
        LocalDateTime startOfDay, endOfDay, startOfMonth, endOfMonth;
        ArrayList<Event> eventsOfDay = new ArrayList<>();
        try {
            // Get start of day and start of next day
            startOfDay = LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE).atStartOfDay();
            endOfDay = startOfDay.plusDays(1);

            startOfMonth = LocalDate.of(startOfDay.getYear(), startOfDay.getMonth(), 1).atStartOfDay();
            endOfMonth = startOfMonth.plusMonths(1);
            logger.debug("[" + currentUser + "] Converted to date object: " + startOfDay.toString());
        } catch (DateTimeParseException e) {
            logger.warn("[" + currentUser + "] Unable to parse date: " + date + ": " + e.toString());
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }

        logger.debug("Getting all single day events...");
        eventsOfDay.addAll(eventRepository.findAllByStartDateTimeBetweenAndMultiDate(startOfDay, endOfDay, false));
        logger.debug("All single day events retrieved, got " + eventsOfDay.size() + " events so far");

        logger.debug("Getting all multi day events...");
        //Collecting all multi date events, that either start or end within the selected month (which means that events that are spanning over several months are not collected)
        eventsOfDay.addAll(Stream.concat(
                eventRepository.findAllByStartDateTimeBetweenAndMultiDate(startOfMonth, endOfMonth, true).stream(), //All multi date events starting within the month
                eventRepository.findAllByEndDateTimeBetweenAndMultiDate(startOfMonth, endOfMonth, true).stream()) //All multi date events ending within the month
                .distinct() //Removing all duplicated events
                .parallel()
                .filter(event -> event.getStartDate().isEqual(startOfDay.toLocalDate())
                        || event.getEndDate().isEqual(startOfDay.toLocalDate())
                        || (event.getEndDate().isAfter(endOfDay.toLocalDate())
                                && event.getStartDate().isBefore(startOfDay.toLocalDate()))) //Filter all multi date events that do not span over the date
                .collect(Collectors.toList()));
        logger.debug("All multi day events gathered, got " + eventsOfDay.size() + " events so far");

        if (eventsOfDay.isEmpty()) {
            logger.warn("[" + currentUser + "] The events list of " + date + " is empty");
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        } else {
            eventsOfDay.replaceAll(Event::getSendingObjectOnlyNameTimeId);
            logger.debug("[" + currentUser + "] Returning " + eventsOfDay.size() + " events for " + date);
            return new ResponseEntity<>(eventsOfDay, HttpStatus.OK);
        }
    }

    /**
     * Returns a specific event based on its id. The function is invoked by GETting the URI the URI /api/admin/event.
     * @param id The id of the selected event.
     * @param currentUser The currently logged in user.
     * @return An HTTP response with a status code. If the function succeeds, the event is returned, otherwise an error code is returned.
     */
    @RequestMapping(produces = "application/json", method = RequestMethod.GET)
    public ResponseEntity<Event> getEvent(@RequestParam String id, @CurrentUser User currentUser) {
        logger.trace("[" + currentUser + "] Getting the event for ID " + id);
        if (id.isEmpty()) {
            logger.warn("[" + currentUser + "] The ID can not be empty.");
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        } else {
            Event selectedEvent = eventRepository.findEventById(id);
            if (selectedEvent == null) {
                logger.warn("[" + currentUser + "] Unable to find specified event.");
                return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
            } else {
                selectedEvent = selectedEvent.getSendingObjectInternalSync(currentUser);
                if (!currentUser.isAllowedToAdministrate(selectedEvent)) {
                    logger.debug("[" + currentUser + "] The user is not allowed to edit the event " + id);
                    selectedEvent.setAdministrationNotAllowedMessage(
                            "You are not allowed to modify this event, since you did not create it.");
                }
                return new ResponseEntity<>(selectedEvent, HttpStatus.OK);
            }
        }
    }

    /**
     * This function saves an event. The function is invoked by POSTint the parameters to the URI /api/admin/event.
     * @param eventFlag This flag either stores the ID of the event, or true, if a new event is created.
     * @param eventName The name of the event.
     * @param eventDescription The description of the event.
     * @param startDate The start date, formatted according to the pattern d/MM/y, defined within the Java 8 DateTimeFormatter.
     * @param startTime The start time, formatted according to the pattern H:m, defined within the Java 8 DateTimeFormatter.
     * @param endDate The end date, formatted according to the pattern d/MM/y, defined within the Java 8 DateTimeFormatter.
     * @param endTime The end time, formatted according to the pattern H:m, defined within the Java 8 DateTimeFormatter.
     * @param location The name of the location of the event.
     * @param locationLat The latitude of the location of the event.
     * @param locationLng The longitude of the location of the event.
     * @param invitedDivisions A comma separated list of invited divisions.
     * @param currentUser The currently logged in user.
     * @return An HTTP response with a status code together with a JSON map object, containing an 'errorMessage', or a 'successMessage' respectively. If the operation was successful the id of the event is accessible via 'eventID'.
     */
    @RequestMapping(method = RequestMethod.POST, produces = "application/json")
    public ResponseEntity<Map<String, String>> saveEvent(@RequestParam String eventFlag,
            @RequestParam String eventName, @RequestParam String eventDescription, @RequestParam String startDate,
            @RequestParam String startTime, @RequestParam String endDate, @RequestParam String endTime,
            @RequestParam String location, @RequestParam String locationLat, @RequestParam String locationLng,
            @RequestParam String invitedDivisions, @CurrentUser User currentUser) {
        logger.trace("[" + currentUser + "] Saving event");
        Map<String, String> responseMap = new HashMap<>();
        Event event;
        if (eventFlag.isEmpty()) {
            logger.warn("[" + currentUser + "] The event flag is empty");
            responseMap.put("errorMessage", "The event flag is not allowed to be empty");
            return new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST);
        } else if (eventFlag.equals("true")) {
            logger.debug("[" + currentUser + "] A new event is created");
            event = new Event();
        } else {
            logger.debug("[" + currentUser + "] The event with id " + eventFlag + " is altered");
            event = eventRepository.findEventById(eventFlag);
            if (event == null) {
                logger.warn("[" + currentUser + "] Unable to find the specified event with id " + eventFlag);
                responseMap.put("errorMessage", "Unable to find the specified event");
                return new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST);
            } else if (!currentUser.isAllowedToAdministrate(event)) {
                logger.warn(
                        "[" + currentUser + "] The user is not allowed to alter the selected event " + eventFlag);
                responseMap.put("errorMessage", "You are not allowed to edit the selected event");
                return new ResponseEntity<>(responseMap, HttpStatus.FORBIDDEN);
            }
        }

        event.setName(eventName);
        event.setDescription(eventDescription);

        if (startDate.isEmpty() || startTime.isEmpty() || endDate.isEmpty() || endTime.isEmpty()) {
            logger.warn("[" + currentUser + "] The date and times defining the event (ID " + eventFlag
                    + ") are not allowed to be empty.");
            responseMap.put("errorMessage", "The date and times defining the event are not allowed to be empty");
            return new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST);
        } else {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d/MM/y'T'H:m");
            try {
                event.setStartDateTime(LocalDateTime.parse(startDate + "T" + startTime, formatter));
            } catch (DateTimeParseException e) {
                logger.warn("[" + currentUser + "] Unrecognized date format " + startDate + "T" + startTime);
                responseMap.put("errorMessage", "Unrecognized date or time format within start time");
                return new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST);
            }

            try {
                event.setEndDateTime(LocalDateTime.parse(endDate + "T" + endTime, formatter));
            } catch (DateTimeParseException e) {
                logger.warn("[" + currentUser + "] Unrecognized date format " + endDate + "T" + endTime);
                responseMap.put("errorMessage", "Unrecognized date or time format within end time");
                return new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST);
            }
        }

        event.setLocation(location);

        if (!locationLat.isEmpty()) {
            try {
                event.setLocationLat(Double.parseDouble(locationLat));
            } catch (NumberFormatException e) {
                logger.warn("[" + currentUser + "] Unable to paste lat " + locationLat);
                responseMap.put("errorMessage", "Unable to parse latitude coordinate");
                return new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST);
            }
        }

        if (!locationLng.isEmpty()) {
            try {
                event.setLocationLng(Double.parseDouble(locationLng));
            } catch (NumberFormatException e) {
                logger.warn("[" + currentUser + "] Unable to paste lng " + locationLng);
                responseMap.put("errorMessage", "Unable to parse longitude coordinate");
                return new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST);
            }
        }

        if (!invitedDivisions.isEmpty()) {
            String[] divArray = invitedDivisions.split(",");
            for (String division : divArray) {
                Division div = divisionRepository.findByName(division);
                if (div == null) {
                    logger.warn("[" + currentUser + "] Unrecognized division (" + division + ")");
                    responseMap.put("errorMessage", "Division " + division + " does not exist");
                    return new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST);
                }
                event.addDivision(div);
            }
            event.updateInvitedUser(divisionRepository);
        } else if (event.getInvitedDivision() != null && !event.getInvitedDivision().isEmpty()) {
            event.updateInvitedUser(divisionRepository);
        }

        //Updating several fields.
        event.setEventAdmin(currentUser);
        event.setLastChanged(LocalDateTime.now());
        event.updateMultiDate();
        try {
            eventRepository.save(event);
            logger.info("[" + currentUser + "] Successfully saved event " + eventFlag);
            responseMap.put("successMessage", "Successfully saved the event");
            responseMap.put("eventID", event.getId());
            return new ResponseEntity<>(responseMap, HttpStatus.OK);
        } catch (ConstraintViolationException e) {
            logger.warn(
                    "[" + currentUser + "] A database constraint was violated while saving the event " + eventFlag);
            responseMap.put("errorMessage", "A database constraint was violated while saving the event");
            return new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST);
        }
    }

    /**
     * This function deletes an event, specified by the event ID. The function is invoked by DELETEing the URI /api/admin/event.
     * @param id The ID of the event, that should be deleted.
     * @param currentUser The currently logged in user.
     * @return An HTTP response with a status code. If an error occurred an error message is bundled into the response, otherwise a success message is available
     */
    @RequestMapping(method = RequestMethod.DELETE)
    public ResponseEntity<String> deleteEvent(@RequestParam String id, @CurrentUser User currentUser) {
        logger.trace("[" + currentUser + "] Deleting event with id " + id);
        if (id.isEmpty()) {
            logger.warn("[" + currentUser + "] The id of an event is not allowed to be empty.");
            return new ResponseEntity<>("The ID of an event is not allowed to be empty", HttpStatus.BAD_REQUEST);
        }

        Event event = eventRepository.findEventById(id);

        if (event == null) {
            logger.warn("[" + currentUser + "] Unable to find the selected event with id " + id);
            return new ResponseEntity<>("Unable to find the selected event", HttpStatus.BAD_REQUEST);
        } else if (!currentUser.isAllowedToAdministrate(event)) {
            logger.warn("[" + currentUser + "] The user is not allowed to modify the event owned by "
                    + event.getEventAdmin());
            return new ResponseEntity<>("You are not allowed to modify the selected event", HttpStatus.FORBIDDEN);
        } else {
            try {
                eventRepository.delete(event);
                logger.info("[" + currentUser + "] Successfully delete the selected event");
                return new ResponseEntity<>("Successfully deleted selected event", HttpStatus.OK);
            } catch (IllegalArgumentException e) {
                logger.warn("[" + currentUser + "] Unable to delete selected event: " + e.getMessage());
                return new ResponseEntity<>("Unable to delete the selected event",
                        HttpStatus.INTERNAL_SERVER_ERROR);
            }
        }
    }
}