io.lavagna.service.CalendarService.java Source code

Java tutorial

Introduction

Here is the source code for io.lavagna.service.CalendarService.java

Source

/**
 * This file is part of lavagna.
 *
 * lavagna 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 3 of the License, or
 * (at your option) any later version.
 *
 * lavagna 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 lavagna.  If not, see <http://www.gnu.org/licenses/>.
 */
package io.lavagna.service;

import static io.lavagna.service.SearchFilter.filter;
import static org.apache.commons.lang3.ObjectUtils.firstNonNull;
import io.lavagna.model.BoardColumn;
import io.lavagna.model.CalendarInfo;
import io.lavagna.model.CardDataHistory;
import io.lavagna.model.CardFullWithCounts;
import io.lavagna.model.CardLabel;
import io.lavagna.model.CardLabel.LabelType;
import io.lavagna.model.ColumnDefinition;
import io.lavagna.model.Key;
import io.lavagna.model.LabelAndValue;
import io.lavagna.model.LabelListValueWithMetadata;
import io.lavagna.model.Project;
import io.lavagna.model.SearchResults;
import io.lavagna.model.User;
import io.lavagna.model.UserWithPermission;
import io.lavagna.model.util.CalendarTokenNotFoundException;

import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import lombok.AllArgsConstructor;
import lombok.Getter;
import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.Dur;
import net.fortuna.ical4j.model.Property;
import net.fortuna.ical4j.model.component.VAlarm;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.parameter.Cn;
import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.Action;
import net.fortuna.ical4j.model.property.CalScale;
import net.fortuna.ical4j.model.property.Created;
import net.fortuna.ical4j.model.property.Description;
import net.fortuna.ical4j.model.property.LastModified;
import net.fortuna.ical4j.model.property.Method;
import net.fortuna.ical4j.model.property.Organizer;
import net.fortuna.ical4j.model.property.ProdId;
import net.fortuna.ical4j.model.property.Uid;
import net.fortuna.ical4j.model.property.Url;
import net.fortuna.ical4j.model.property.Version;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly = true)
public class CalendarService {

    private final ConfigurationRepository configurationRepository;
    private final SearchService searchService;
    private final UserRepository userRepository;
    private final UserService userService;
    private final CardDataService cardDataService;
    private final ProjectService projectService;
    private final CardLabelRepository cardLabelRepository;

    public CalendarService(ConfigurationRepository configurationRepository, SearchService searchService,
            UserService userService, UserRepository userRepository, CardDataService cardDataService,
            ProjectService projectService, CardLabelRepository cardLabelRepository) {
        this.configurationRepository = configurationRepository;
        this.searchService = searchService;
        this.userRepository = userRepository;
        this.userService = userService;
        this.cardDataService = cardDataService;
        this.projectService = projectService;
        this.cardLabelRepository = cardLabelRepository;
    }

    @Transactional(readOnly = false)
    public void setCalendarFeedDisabled(User user, boolean isDisabled) {
        userRepository.setCalendarFeedDisabled(user, isDisabled);
    }

    @Transactional(readOnly = false)
    public CalendarInfo findCalendarInfoFromUser(User user) {
        try {
            return userRepository.findCalendarInfoFromUserId(user);
        } catch (CalendarTokenNotFoundException ex) {
            String token = UUID.randomUUID().toString();// <- this use secure random
            String hashedToken = DigestUtils.sha256Hex(token);
            userRepository.registerCalendarToken(user, hashedToken);
            return findCalendarInfoFromUser(user);
        }
    }

    private UserWithPermission findUserFromCalendarToken(String token) {
        int userId = userRepository.findUserIdFromCalendarToken(token);
        return userService.findUserWithPermission(userId);
    }

    private long getLong(int x, int y) {
        return (((long) x) << 32) | (y & 0xffffffffL);
    }

    private String getEventName(CardFullWithCounts card) {
        return String.format("%s-%s %s (%s)", card.getBoardShortName(), card.getSequence(), card.getName(),
                card.getColumnDefinition());
    }

    private UserDescription getUserDescription(int userId, Map<Integer, UserDescription> cache) {
        if (!cache.containsKey(userId)) {
            User u = userRepository.findById(userId);
            String name = firstNonNull(u.getDisplayName(), u.getEmail(), u.getUsername());
            String email = String.format("MAILTO:%s", firstNonNull(u.getEmail(), "unknown@unknown.com"));
            cache.put(userId, new UserDescription(name, email));
        }
        return cache.get(userId);
    }

    public Calendar getUserCalendar(String userToken) throws URISyntaxException, ParseException {
        UserWithPermission user;

        try {
            user = findUserFromCalendarToken(userToken);
        } catch (EmptyResultDataAccessException ex) {
            throw new SecurityException("Invalid token");
        }

        if (userRepository.isCalendarFeedDisabled(user)) {
            throw new SecurityException("Calendar feed disabled");
        }

        final Calendar calendar = new Calendar();
        calendar.getProperties().add(new ProdId("-//Lavagna//iCal4j 1.0//EN"));
        calendar.getProperties().add(Version.VERSION_2_0);
        calendar.getProperties().add(CalScale.GREGORIAN);
        calendar.getProperties().add(Method.PUBLISH);

        final String applicationUrl = StringUtils
                .appendIfMissing(configurationRepository.getValue(Key.BASE_APPLICATION_URL), "/");

        final List<VEvent> events = new ArrayList<>();

        final SimpleDateFormat releaseDateFormatter = new SimpleDateFormat("dd.MM.yyyy HH:mm");

        // Milestones
        List<Project> projects = projectService.findAllProjects(user);
        for (Project project : projects) {
            CardLabel milestoneLabel = cardLabelRepository.findLabelByName(project.getId(), "MILESTONE",
                    CardLabel.LabelDomain.SYSTEM);

            Url mUrl = new Url(new URI(String.format("%s%s/milestones/", applicationUrl, project.getShortName())));

            for (LabelListValueWithMetadata m : cardLabelRepository
                    .findListValuesByLabelId(milestoneLabel.getId())) {
                if (m.getMetadata().containsKey("releaseDate")) {

                    java.util.Date date = releaseDateFormatter.parse(m.getMetadata().get("releaseDate") + " 12:00");

                    SearchFilter filter = filter(SearchFilter.FilterType.MILESTONE, SearchFilter.ValueType.STRING,
                            m.getValue());
                    SearchFilter notTrashFilter = filter(SearchFilter.FilterType.NOTLOCATION,
                            SearchFilter.ValueType.STRING, BoardColumn.BoardColumnLocation.TRASH.toString());
                    SearchResults cards = searchService.find(Arrays.asList(filter, notTrashFilter), project.getId(),
                            null, user);

                    double closed = 0;
                    double total = 0;
                    StringBuilder descBuilder = new StringBuilder();
                    for (CardFullWithCounts card : cards.getFound()) {
                        if (card.getColumnDefinition() == ColumnDefinition.CLOSED) {
                            closed++;
                        }
                        total++;
                        descBuilder.append(getEventName(card));
                        descBuilder.append("\n");
                    }

                    final String name = String.format("%s - %s (%.0f%%)", project.getShortName(), m.getValue(),
                            total > 0 ? 100 * closed / total : 100);

                    final VEvent event = new VEvent(new Date(date.getTime()), name);
                    event.getProperties().getProperty(Property.DTSTART).getParameters().add(Value.DATE);

                    event.getProperties().add(new Description(descBuilder.toString()));

                    final UUID id = new UUID(getLong(m.getCardLabelId(), m.getId()), getLong(m.getOrder(), 0));
                    event.getProperties().add(new Uid(id.toString()));

                    // Reminder on milestone's date
                    if (!m.getMetadata().containsKey("status") || m.getMetadata().get("status").equals("CLOSED")) {
                        final VAlarm reminder = new VAlarm(new Dur(0, 0, 0, 0));
                        reminder.getProperties().add(Action.DISPLAY);
                        reminder.getProperties().add(new Description(name));
                        event.getAlarms().add(reminder);
                    }

                    // Url
                    event.getProperties().add(mUrl);

                    events.add(event);
                }
            }
        }

        // Cards
        Map<Integer, UserDescription> usersCache = new HashMap<>();
        Map<Integer, CardFullWithCounts> map = new LinkedHashMap<>();

        SearchFilter locationFilter = filter(SearchFilter.FilterType.LOCATION, SearchFilter.ValueType.STRING,
                BoardColumn.BoardColumnLocation.BOARD.toString());

        SearchFilter aFilter = filter(SearchFilter.FilterType.ASSIGNED, SearchFilter.ValueType.CURRENT_USER, "me");
        for (CardFullWithCounts card : searchService.find(Arrays.asList(locationFilter, aFilter), null, null, user)
                .getFound()) {
            map.put(card.getId(), card);
        }

        SearchFilter wFilter = filter(SearchFilter.FilterType.WATCHED_BY, SearchFilter.ValueType.CURRENT_USER,
                "me");
        for (CardFullWithCounts card : searchService.find(Arrays.asList(locationFilter, wFilter), null, null, user)
                .getFound()) {
            map.put(card.getId(), card);
        }

        for (CardFullWithCounts card : map.values()) {

            Url cardUrl = new Url(new URI(String.format("%s%s/%s-%s", applicationUrl, card.getProjectShortName(),
                    card.getBoardShortName(), card.getSequence())));

            CardDataHistory cardDesc = cardDataService.findLatestDescriptionByCardId(card.getId());

            for (LabelAndValue lav : card.getLabelsWithType(LabelType.TIMESTAMP)) {
                String name = getEventName(card);

                final VEvent event = new VEvent(new Date(lav.getLabelValueTimestamp()), name);
                event.getProperties().getProperty(Property.DTSTART).getParameters().add(Value.DATE);

                event.getProperties().add(new Created(new DateTime(card.getCreationDate())));
                event.getProperties().add(new LastModified(new DateTime(card.getLastUpdateTime())));

                final UUID id = new UUID(getLong(card.getColumnId(), card.getId()),
                        getLong(lav.getLabelId(), lav.getLabelValueId()));
                event.getProperties().add(new Uid(id.toString()));

                // Reminder on label's date
                if (card.getColumnDefinition() != ColumnDefinition.CLOSED) {
                    final VAlarm reminder = new VAlarm(new Dur(0, 0, 0, 0));
                    reminder.getProperties().add(Action.DISPLAY);
                    reminder.getProperties().add(new Description(name));
                    event.getAlarms().add(reminder);
                }

                // Organizer
                UserDescription ud = getUserDescription(card.getCreationUser(), usersCache);
                Organizer organizer = new Organizer(URI.create(ud.getEmail()));
                organizer.getParameters().add(new Cn(ud.getName()));
                event.getProperties().add(organizer);

                // Url
                event.getProperties().add(cardUrl);

                // Description
                if (cardDesc != null) {
                    event.getProperties().add(new Description(cardDesc.getContent()));
                }

                events.add(event);
            }
        }

        calendar.getComponents().addAll(events);

        return calendar;
    }

    @Getter
    @AllArgsConstructor
    static class UserDescription {
        private String name;
        private String email;
    }
}