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; 041import org.ametys.plugins.workspaces.calendars.task.TaskCalendarEvent; 042import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint; 043import org.ametys.plugins.workspaces.project.objects.Project; 044import org.ametys.plugins.workspaces.tasks.Task; 045import org.ametys.plugins.workspaces.tasks.TasksWorkspaceModule; 046 047/** 048 * Helper to convert events to JSON 049 */ 050public class CalendarEventJSONHelper extends AbstractCalendarDAO 051{ 052 /** Avalon Role */ 053 public static final String ROLE = CalendarEventJSONHelper.class.getName(); 054 055 private static final String __HOUR_PATTERN = "yyyyMMdd'T'HHmmssXXX"; 056 057// private static final String __FULL_DAY_PATTERN = "uuuuMMdd"; 058 059 /** Calendar DAO */ 060 protected CalendarDAO _calendarDAO; 061 062 private TasksWorkspaceModule _taskModule; 063 064 @Override 065 public void service(ServiceManager manager) throws ServiceException 066 { 067 super.service(manager); 068 _calendarDAO = (CalendarDAO) manager.lookup(CalendarDAO.ROLE); 069 WorkspaceModuleExtensionPoint moduleManagerEP = (WorkspaceModuleExtensionPoint) manager.lookup(WorkspaceModuleExtensionPoint.ROLE); 070 _taskModule = moduleManagerEP.getModule(TasksWorkspaceModule.TASK_MODULE_ID); 071 } 072 073 /** 074 * Get event info for a specific occurrence 075 * @param event The event 076 * @param occurrenceDate the occurrence 077 * @param fullInfo true to include full info (rights, parent id, etc...) 078 * @return the event data in a map 079 */ 080 public Map<String, Object> eventAsJsonWithOccurrence(CalendarEvent event, ZonedDateTime occurrenceDate, boolean fullInfo) 081 { 082 Map<String, Object> eventData = eventAsJson(event, fullInfo, false); 083 084 Optional<CalendarEventOccurrence> optionalEvent = event.getFirstOccurrence(occurrenceDate); 085 if (optionalEvent.isPresent()) 086 { 087 eventData.putAll(optionalEvent.get().toJSON()); 088 } 089 090 return eventData; 091 } 092 093 /** 094 * Get event info 095 * @param event The event 096 * @param fullInfo true to include full info (rights, parent id, etc...) 097 * @param startDate The start date. 098 * @param endDate The end date. 099 * @return the event data in a map 100 */ 101 public Map<String, Object> eventAsJsonWithOccurrences(CalendarEvent event, boolean fullInfo, ZonedDateTime startDate, ZonedDateTime endDate) 102 { 103 Map<String, Object> eventData = eventAsJson(event, false, false); 104 105 List<CalendarEventOccurrence> occurences = event.getOccurrences(startDate, endDate); 106 107 List<Object> occurrencesDataList = new ArrayList<>(); 108 eventData.put("occurrences", occurrencesDataList); 109 110 for (CalendarEventOccurrence occurence : occurences) 111 { 112 occurrencesDataList.add(occurence.toJSON()); 113 } 114 return eventData; 115 } 116 117 /** 118 * Get event info 119 * @param event The event 120 * @param fullInfo true to include full info (rights, parent id, etc...) 121 * @param useICSFormat true to use ICS Format for dates 122 * @return the event data in a map 123 */ 124 public Map<String, Object> eventAsJson(CalendarEvent event, boolean fullInfo, boolean useICSFormat) 125 { 126 127 Calendar calendar = event.getCalendar(); 128 Map<String, Object> result = new HashMap<>(); 129 130 result.put("id", event.getId()); 131 result.put("calendarId", calendar.getId()); 132 result.put("color", calendar.getColor()); 133 134 result.put("title", event.getTitle()); 135 result.put("description", event.getDescription()); 136 137 boolean fullDay = event.getFullDay(); 138 139 result.put("fullDay", fullDay); 140 result.put("recurrenceType", event.getRecurrenceType().toString()); 141 142 result.put("location", event.getLocation()); 143 result.put("keywords", event.getTags()); 144 145 ZonedDateTime untilDate = event.getRepeatUntil(); 146 if (untilDate != null) 147 { 148 if (useICSFormat) 149 { 150 // iCalendar/ICS handle until date slightly differently, so we have to add one day to include the last occurrence 151 untilDate = untilDate.plusDays(1).minusSeconds(1); 152 } 153 154 result.put("untilDate", formatDate(untilDate, useICSFormat, fullDay)); 155 } 156 157 ZonedDateTime startDateEvent = event.getStartDate(); 158 ZonedDateTime endDateEvent = event.getEndDate(); 159 160 if (event.getFullDay()) 161 { 162 result.put("endDateNextDay", formatDate(endDateEvent.plusDays(1), useICSFormat, fullDay)); 163 } 164 165 result.put("startDate", formatDate(startDateEvent, useICSFormat, fullDay)); 166 167 if (event.getFullDay() && useICSFormat) 168 { 169 result.put("endDate", formatDate(endDateEvent.plusDays(1), useICSFormat, fullDay)); 170 } 171 else 172 { 173 result.put("endDate", formatDate(endDateEvent, useICSFormat, fullDay)); 174 } 175 176 //excluded occurences 177 List<ZonedDateTime> excludedOccurences = event.getExcludedOccurences(); 178 if (excludedOccurences != null && !excludedOccurences.isEmpty()) 179 { 180 List<String> excludedOccurencesStrings = new ArrayList<>(); 181 for (ZonedDateTime excludedOccurence : excludedOccurences) 182 { 183 excludedOccurencesStrings.add(formatDate(excludedOccurence, useICSFormat, true)); 184 } 185 result.put("excludedDates", excludedOccurencesStrings); 186 } 187 188 // creator 189 UserIdentity creatorIdentity = event.getCreator(); 190 User creator = _userManager.getUser(creatorIdentity); 191 192 result.put("creator", creatorIdentity); 193 result.put("creatorFullName", creator != null ? creator.getFullName() : creatorIdentity.getLogin()); 194 195 UserIdentity user = _currentUserProvider.getUser(); 196 result.put("isCreator", creatorIdentity.equals(user)); 197 result.put("creationDate", formatDate(event.getCreationDate(), useICSFormat, false)); 198 199 // last modification 200 UserIdentity contributorIdentity = event.getLastContributor(); 201 User contributor = _userManager.getUser(contributorIdentity); 202 203 result.put("contributor", contributorIdentity); 204 result.put("contributorFullName", contributor != null ? contributor.getFullName() : contributorIdentity.getLogin()); 205 result.put("lastModified", formatDate(event.getLastModified(), useICSFormat, false)); 206 207 if (fullInfo) 208 { 209 result.putAll(_eventAsJsonFullInfo(event)); 210 } 211 212 result.put("calendar", _calendarDAO.getCalendarProperties(calendar)); 213 214 // tags and places are expected by the client (respectively keywords and location on the server side) 215 result.put("tags", event.getTags()); 216 217 String location = StringUtils.defaultString(event.getLocation()); 218 result.put("location", location); 219 result.put("places", Stream.of(location.split(",")).filter(StringUtils::isNotEmpty).collect(Collectors.toList())); 220 221 // add event rights 222 result.put("rights", _extractEventRightData(event)); 223 224 result.put("resourceIds", event.getResources()); 225 226 result.put("isModifiable", event instanceof ModifiableCalendarEvent); 227 if (event instanceof TaskCalendarEvent taskCalendarEvent) 228 { 229 Task task = taskCalendarEvent.getTask(); 230 Project project = _projectManager.getParentProject(task); 231 result.put("taskURL", _taskModule.getTaskUri(project, task.getId())); 232 233 } 234 235 return result; 236 } 237 238 private String formatDate(ZonedDateTime date, boolean useICSFormat, boolean fullDay) 239 { 240 if (useICSFormat) 241 { 242 if (fullDay) 243 { 244 // FIXME : commented because currently, dates are badly stored, waiting for a data migration to use it back 245 // return DateUtils.zonedDateTimeToString(date, date.getZone(), __FULL_DAY_PATTERN); 246 return DateUtils.zonedDateTimeToString(date); 247 } 248 else 249 { 250 return DateUtils.zonedDateTimeToString(date, date.getZone(), __HOUR_PATTERN); 251 } 252 } 253 else 254 { 255 return DateUtils.zonedDateTimeToString(date); 256 } 257 } 258 259 /** 260 * Retrieves the event additional info (rights, parent id, etc...) 261 * @param event The event 262 * @return the event additional info (rights, parent id, etc...) in a map 263 */ 264 protected Map<String, Object> _eventAsJsonFullInfo(CalendarEvent event) 265 { 266 Map<String, Object> result = new HashMap<>(); 267 268 ExplorerNode explorerNode = event.getParent(); 269 ExplorerNode root = explorerNode; 270 while (true) 271 { 272 if (root.getParent() instanceof ExplorerNode) 273 { 274 root = root.getParent(); 275 } 276 else 277 { 278 break; 279 } 280 } 281 result.put("rootId", root.getId()); 282 result.put("parentId", explorerNode.getId()); 283 result.put("name", event.getName()); 284 result.put("path", explorerNode.getExplorerPath()); 285 286 result.put("rights", _getUserRights(explorerNode)); 287 288 return result; 289 } 290 291 /** 292 * Internal method to extract the data concerning the right of the current user for an event 293 * @param event The event 294 * @return The map of right data. Keys are the rights id, and values indicates whether the current user has the right or not. 295 */ 296 protected Map<String, Object> _extractEventRightData(CalendarEvent event) 297 { 298 Map<String, Object> rightsData = new HashMap<>(); 299 UserIdentity user = _currentUserProvider.getUser(); 300 Calendar calendar = event.getCalendar(); 301 302 rightsData.put("edit", event instanceof ModifiableCalendarEvent && _rightManager.hasRight(user, AbstractCalendarDAO.RIGHTS_EVENT_EDIT, calendar) == RightResult.RIGHT_ALLOW); 303 rightsData.put("delete", event instanceof ModifiableCalendarEvent && _rightManager.hasRight(user, AbstractCalendarDAO.RIGHTS_EVENT_DELETE, calendar) == RightResult.RIGHT_ALLOW); 304 rightsData.put("delete-own", event instanceof ModifiableCalendarEvent && _rightManager.hasRight(user, AbstractCalendarDAO.RIGHTS_EVENT_DELETE_OWN, calendar) == RightResult.RIGHT_ALLOW); 305 306 return rightsData; 307 } 308 309 /** 310 * Get the user rights on the resource collection 311 * @param node The explorer node 312 * @return The user's rights 313 */ 314 protected Set<String> _getUserRights(ExplorerNode node) 315 { 316 return _rightManager.getUserRights(_currentUserProvider.getUser(), node); 317 } 318 319}