001/*
002 *  Copyright 2022 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016
017package org.ametys.plugins.workspaces.calendars.events;
018
019import java.time.ZonedDateTime;
020import java.util.ArrayList;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Optional;
025import java.util.Set;
026import java.util.stream.Collectors;
027import java.util.stream.Stream;
028
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.commons.lang3.StringUtils;
032
033import org.ametys.core.right.RightManager.RightResult;
034import org.ametys.core.user.User;
035import org.ametys.core.user.UserIdentity;
036import org.ametys.core.util.DateUtils;
037import org.ametys.plugins.explorer.ExplorerNode;
038import org.ametys.plugins.workspaces.calendars.AbstractCalendarDAO;
039import org.ametys.plugins.workspaces.calendars.Calendar;
040import org.ametys.plugins.workspaces.calendars.CalendarDAO;
041
042/**
043 * Helper to convert events to JSON
044 */
045public class CalendarEventJSONHelper extends AbstractCalendarDAO
046{
047    /** Avalon Role */
048    public static final String ROLE = CalendarEventJSONHelper.class.getName();
049
050    private static final String __HOUR_PATTERN = "yyyyMMdd'T'HHmmssXXX";
051    
052//    private static final String __FULL_DAY_PATTERN = "uuuuMMdd";
053    
054    
055    /** Calendar DAO */
056    protected CalendarDAO _calendarDAO;
057    
058    @Override
059    public void service(ServiceManager manager) throws ServiceException
060    {
061        super.service(manager);
062        _calendarDAO = (CalendarDAO) manager.lookup(CalendarDAO.ROLE);
063    }
064        
065    /**
066     * Get event info for a specific occurrence
067     * @param event The event
068     * @param occurrenceDate the occurrence
069     * @param fullInfo true to include full info (rights, parent id, etc...)
070     * @return the event data in a map
071     */
072    public Map<String, Object> eventAsJsonWithOccurrence(CalendarEvent event, ZonedDateTime occurrenceDate, boolean fullInfo)
073    {
074        Map<String, Object> eventData = eventAsJson(event, fullInfo, false);
075        
076        Optional<CalendarEventOccurrence> optionalEvent = event.getFirstOccurrence(occurrenceDate);
077        if (optionalEvent.isPresent())
078        {
079            eventData.putAll(optionalEvent.get().toJSON());
080        }
081        
082        return eventData;
083    }
084    
085    /**
086     * Get event info
087     * @param event The event
088     * @param fullInfo true to include full info (rights, parent id, etc...)
089     * @param startDate The start date.
090     * @param endDate The end date.
091     * @return the event data in a map
092     */
093    public Map<String, Object> eventAsJsonWithOccurrences(CalendarEvent event, boolean fullInfo, ZonedDateTime startDate, ZonedDateTime endDate)
094    {
095        Map<String, Object> eventData = eventAsJson(event, false, false);
096
097        List<CalendarEventOccurrence> occurences = event.getOccurrences(startDate, endDate);
098        
099        List<Object> occurrencesDataList = new ArrayList<>();
100        eventData.put("occurrences", occurrencesDataList);
101        
102        for (CalendarEventOccurrence occurence : occurences)
103        {
104            occurrencesDataList.add(occurence.toJSON());
105        }
106        return eventData;
107    }
108    
109    /**
110     * Get event info
111     * @param event The event
112     * @param fullInfo true to include full info (rights, parent id, etc...)
113     * @param useICSFormat true to use ICS Format for dates
114     * @return the event data in a map
115     */
116    public Map<String, Object> eventAsJson(CalendarEvent event, boolean fullInfo, boolean useICSFormat)
117    {
118        
119        Calendar calendar = event.getParent();
120        Map<String, Object> result = new HashMap<>();
121        
122        result.put("id", event.getId());
123        result.put("calendarId", event.getParent().getId());
124        result.put("color", calendar.getColor());
125        
126        result.put("title", event.getTitle());
127        result.put("description", event.getDescription());
128        
129        boolean fullDay = event.getFullDay();
130        
131        result.put("fullDay", fullDay);
132        result.put("recurrenceType", event.getRecurrenceType().toString());
133        
134        result.put("location", event.getLocation());
135        result.put("keywords", event.getTags());
136        
137        ZonedDateTime untilDate = event.getRepeatUntil();
138        if (untilDate != null)
139        {
140            if (useICSFormat)
141            {
142                // iCalendar/ICS handle until date slightly differently, so we have to add one day to include the last occurrence
143                untilDate = untilDate.plusDays(1).minusSeconds(1);
144            }
145            
146            result.put("untilDate", formatDate(untilDate, useICSFormat, fullDay));
147        }
148        
149        ZonedDateTime startDateEvent = event.getStartDate();
150        ZonedDateTime endDateEvent = event.getEndDate();
151        
152        if (event.getFullDay())
153        {
154            result.put("endDateNextDay", formatDate(endDateEvent.plusDays(1), useICSFormat, fullDay));
155        }
156
157        result.put("startDate", formatDate(startDateEvent, useICSFormat, fullDay));
158        
159        if (event.getFullDay() && useICSFormat)
160        {
161            result.put("endDate", formatDate(endDateEvent.plusDays(1), useICSFormat, fullDay));
162        }
163        else
164        {
165            result.put("endDate", formatDate(endDateEvent, useICSFormat, fullDay));
166        }
167        
168        //excluded occurences
169        List<ZonedDateTime> excludedOccurences = event.getExcludedOccurences();
170        if (excludedOccurences != null && !excludedOccurences.isEmpty())
171        {
172            List<String> excludedOccurencesStrings = new ArrayList<>();
173            for (ZonedDateTime excludedOccurence : excludedOccurences)
174            {
175                excludedOccurencesStrings.add(formatDate(excludedOccurence, useICSFormat, true));
176            }
177            result.put("excludedDates", excludedOccurencesStrings);
178        }
179
180        // creator
181        UserIdentity creatorIdentity = event.getCreator();
182        User creator = _userManager.getUser(creatorIdentity);
183        
184        result.put("creator", creatorIdentity);
185        result.put("creatorFullName", creator != null ? creator.getFullName() : creatorIdentity.getLogin());
186
187        UserIdentity user = _currentUserProvider.getUser();
188        result.put("isCreator", creatorIdentity.equals(user));
189        result.put("creationDate", formatDate(event.getCreationDate(), useICSFormat, false));
190        
191        // last modification
192        UserIdentity contributorIdentity = event.getLastContributor();
193        User contributor = _userManager.getUser(contributorIdentity);
194        
195        result.put("contributor", contributorIdentity);
196        result.put("contributorFullName", contributor != null ? contributor.getFullName() : contributorIdentity.getLogin());
197        result.put("lastModified", formatDate(event.getLastModified(), useICSFormat, false));
198        
199        if (fullInfo)
200        {
201            result.putAll(_eventAsJsonFullInfo(event));
202        }
203
204        result.put("calendar", _calendarDAO.getCalendarProperties(calendar)); 
205        
206        // tags and places are expected by the client (respectively keywords and location on the server side)
207        result.put("tags", event.getTags());
208        
209        String location = StringUtils.defaultString(event.getLocation());
210        result.put("location", location);
211        result.put("places", Stream.of(location.split(",")).filter(StringUtils::isNotEmpty).collect(Collectors.toList()));
212        
213        // add event rights
214        result.put("rights", _extractEventRightData(event));
215        
216        result.put("resourceIds", event.getResources());
217        
218        return result;
219    }
220    
221    private String formatDate(ZonedDateTime date, boolean useICSFormat, boolean fullDay)
222    {
223        if (useICSFormat)
224        {
225            if (fullDay)
226            {
227                // FIXME : commented because currently, dates are badly stored, waiting for a data migration to use it back
228            //  return DateUtils.zonedDateTimeToString(date, date.getZone(), __FULL_DAY_PATTERN);
229                return DateUtils.zonedDateTimeToString(date);
230            }
231            else
232            {
233                return DateUtils.zonedDateTimeToString(date, date.getZone(), __HOUR_PATTERN);
234            }
235        }
236        else
237        {
238            return DateUtils.zonedDateTimeToString(date);
239        }
240    }
241    
242    /**
243     * Retrieves the event additional info (rights, parent id, etc...)
244     * @param event The event
245     * @return the event additional info (rights, parent id, etc...) in a map
246     */
247    protected Map<String, Object> _eventAsJsonFullInfo(CalendarEvent event)
248    {
249        Map<String, Object> result = new HashMap<>();
250        
251        ExplorerNode explorerNode = event.getParent();
252        ExplorerNode root = explorerNode;
253        while (true)
254        {
255            if (root.getParent() instanceof ExplorerNode)
256            {
257                root = root.getParent();
258            }
259            else
260            {
261                break;
262            }
263        }
264        result.put("rootId", root.getId());
265        result.put("parentId", explorerNode.getId());
266        result.put("name", event.getName());
267        result.put("path", explorerNode.getExplorerPath());
268        result.put("isModifiable", true);
269        
270        result.put("rights", _getUserRights(explorerNode));
271        
272        return result;
273    }
274    
275    /**
276     * Internal method to extract the data concerning the right of the current user for an event
277     * @param event The event
278     * @return The map of right data. Keys are the rights id, and values indicates whether the current user has the right or not.
279     */
280    protected  Map<String, Object> _extractEventRightData(CalendarEvent event)
281    {
282        Map<String, Object> rightsData = new HashMap<>();
283        UserIdentity user = _currentUserProvider.getUser();
284        Calendar calendar = event.getParent();
285        
286        rightsData.put("edit", _rightManager.hasRight(user, AbstractCalendarDAO.RIGHTS_EVENT_EDIT, calendar) == RightResult.RIGHT_ALLOW);
287        rightsData.put("delete", _rightManager.hasRight(user, AbstractCalendarDAO.RIGHTS_EVENT_DELETE, calendar) == RightResult.RIGHT_ALLOW);
288        rightsData.put("delete-own", _rightManager.hasRight(user, AbstractCalendarDAO.RIGHTS_EVENT_DELETE_OWN, calendar) == RightResult.RIGHT_ALLOW);
289        
290        return rightsData;
291    }
292    
293    /**
294     * Get the user rights on the resource collection
295     * @param node The explorer node
296     * @return The user's rights
297     */
298    protected Set<String> _getUserRights(ExplorerNode node)
299    {
300        return _rightManager.getUserRights(_currentUserProvider.getUser(), node);
301    }
302    
303}