001/* 002 * Copyright 2015 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 */ 016package org.ametys.plugins.workspaces.calendars; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.LinkedList; 021import java.util.List; 022import java.util.Map; 023import java.util.UUID; 024 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.commons.lang.BooleanUtils; 028import org.apache.commons.lang.IllegalClassException; 029import org.apache.commons.lang3.StringUtils; 030import org.apache.jackrabbit.util.Text; 031 032import org.ametys.core.observation.Event; 033import org.ametys.core.right.RightManager.RightResult; 034import org.ametys.core.ui.Callable; 035import org.ametys.core.user.UserIdentity; 036import org.ametys.plugins.explorer.ModifiableExplorerNode; 037import org.ametys.plugins.explorer.ObservationConstants; 038import org.ametys.plugins.repository.AmetysObject; 039import org.ametys.plugins.repository.AmetysObjectIterable; 040import org.ametys.plugins.repository.AmetysObjectIterator; 041import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 042import org.ametys.plugins.repository.query.QueryHelper; 043import org.ametys.plugins.repository.query.expression.Expression; 044import org.ametys.plugins.repository.query.expression.Expression.Operator; 045import org.ametys.plugins.repository.query.expression.StringExpression; 046import org.ametys.plugins.workspaces.calendars.Calendar.CalendarVisibility; 047import org.ametys.plugins.workspaces.calendars.events.CalendarEvent; 048import org.ametys.plugins.workspaces.calendars.events.CalendarEventJSONHelper; 049import org.ametys.plugins.workspaces.calendars.jcr.JCRCalendar; 050import org.ametys.plugins.workspaces.calendars.jcr.JCRCalendarFactory; 051import org.ametys.plugins.workspaces.project.ProjectConstants; 052 053/** 054 * Calendar DAO 055 */ 056public class CalendarDAO extends AbstractCalendarDAO 057{ 058 /** Avalon Role */ 059 public static final String ROLE = CalendarDAO.class.getName(); 060 061 /** The tasks list JSON helper */ 062 protected CalendarEventJSONHelper _calendarEventJSONHelper; 063 064 @Override 065 public void service(ServiceManager manager) throws ServiceException 066 { 067 super.service(manager); 068 _calendarEventJSONHelper = (CalendarEventJSONHelper) manager.lookup(CalendarEventJSONHelper.ROLE); 069 } 070 071 /** 072 * Get calendar info 073 * @param calendar The calendar 074 * @param recursive True to get data for sub calendars 075 * @param includeEvents True to also include child events 076 * @param useICSFormat true to use ICS Format for dates 077 * @return the calendar data in a map 078 */ 079 public Map<String, Object> getCalendarData(Calendar calendar, boolean recursive, boolean includeEvents, boolean useICSFormat) 080 { 081 Map<String, Object> result = new HashMap<>(); 082 083 result.put("id", calendar.getId()); 084 result.put("title", Text.unescapeIllegalJcrChars(calendar.getName())); 085 result.put("description", calendar.getDescription()); 086 result.put("templateDesc", calendar.getTemplateDescription()); 087 result.put("color", calendar.getColor()); 088 result.put("visibility", calendar.getVisibility().name().toLowerCase()); 089 result.put("public", calendar.getVisibility() == CalendarVisibility.PUBLIC); 090 result.put("workflowName", calendar.getWorkflowName()); 091 092 if (recursive) 093 { 094 List<Map<String, Object>> calendarList = new LinkedList<>(); 095 result.put("calendars", calendarList); 096 AmetysObjectIterable<AmetysObject> children = calendar.getChildren(); 097 for (AmetysObject child : children) 098 { 099 if (child instanceof Calendar) 100 { 101 calendarList.add(getCalendarData((Calendar) child, recursive, includeEvents, useICSFormat)); 102 } 103 } 104 } 105 106 if (includeEvents) 107 { 108 List<Map<String, Object>> eventList = new LinkedList<>(); 109 result.put("events", eventList); 110 111 AmetysObjectIterable<AmetysObject> children = calendar.getChildren(); 112 for (AmetysObject child : children) 113 { 114 if (child instanceof CalendarEvent) 115 { 116 eventList.add(_calendarEventJSONHelper.eventAsJson((CalendarEvent) child, false, useICSFormat)); 117 } 118 } 119 } 120 121 result.put("rights", _extractCalendarRightData(calendar)); 122 result.put("token", getCalendarIcsToken(calendar, true)); 123 124 125 return result; 126 } 127 128 /** 129 * Get calendar info 130 * @param calendar The calendar 131 * @return the calendar data in a map 132 */ 133 public Map<String, Object> getCalendarProperties(Calendar calendar) 134 { 135 return getCalendarData(calendar, false, false, false); 136 } 137 /** 138 * Add a calendar 139 * @param inputName The desired name for the calendar 140 * @param color The calendar color 141 * @param isPublic true if the calendar is public 142 * @return The result map with id, parentId and name keys 143 * @throws IllegalAccessException If the user has no sufficient rights 144 */ 145 @Callable 146 public Map<String, Object> addCalendar(String inputName, String color, boolean isPublic) throws IllegalAccessException 147 { 148 String rootId = _getCalendarRoot().getId(); 149 return addCalendar(rootId, inputName, StringUtils.EMPTY, StringUtils.EMPTY, color, isPublic ? CalendarVisibility.PUBLIC.name() : CalendarVisibility.PRIVATE.name(), "calendar-default", false); 150 } 151 152 /** 153 * Add a calendar 154 * @param id The identifier of the parent in which the calendar will be added 155 * @param inputName The desired name for the calendar 156 * @param description The calendar description 157 * @param templateDesc The calendar template description 158 * @param color The calendar color 159 * @param visibility The calendar visibility 160 * @param workflowName The calendar workflow name 161 * @param renameIfExists True to rename if existing 162 * @return The result map with id, parentId and name keys 163 * @throws IllegalAccessException If the user has no sufficient rights 164 */ 165 public Map<String, Object> addCalendar(String id, String inputName, String description, String templateDesc, String color, String visibility, String workflowName, Boolean renameIfExists) throws IllegalAccessException 166 { 167 return addCalendar(_resolver.resolveById(id), inputName, description, templateDesc, color, visibility, workflowName, renameIfExists, true, true); 168 } 169 170 /** 171 * Add a calendar 172 * @param parent The parent in which the calendar will be added 173 * @param inputName The desired name for the calendar 174 * @param description The calendar description 175 * @param templateDesc The calendar template description 176 * @param color The calendar color 177 * @param visibility The calendar visibility 178 * @param workflowName The calendar workflow name 179 * @param renameIfExists True to rename if existing 180 * @param checkRights true to check if the current user have enough rights to create the calendar 181 * @param notify True to notify the calendar creation 182 * @return The result map with id, parentId and name keys 183 * @throws IllegalAccessException If the user has no sufficient rights 184 */ 185 public Map<String, Object> addCalendar(ModifiableTraversableAmetysObject parent, String inputName, String description, String templateDesc, String color, String visibility, String workflowName, Boolean renameIfExists, Boolean checkRights, boolean notify) throws IllegalAccessException 186 { 187 String originalName = Text.escapeIllegalJcrChars(inputName); 188 189 // Check user right 190 if (checkRights) 191 { 192 _explorerResourcesDAO.checkUserRight(parent, RIGHTS_CALENDAR_ADD); 193 } 194 195 if (BooleanUtils.isNotTrue(renameIfExists) && parent.hasChild(originalName)) 196 { 197 getLogger().warn("Cannot create the calendar with name '" + originalName + "', an object with same name already exists."); 198 return Map.of("message", "already-exist"); 199 } 200 201 if (!_explorerResourcesDAO.checkLock(parent)) 202 { 203 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to modify the object '" + parent.getName() + "' but it is locked by another user"); 204 return Map.of("message", "locked"); 205 } 206 207 int index = 2; 208 String name = originalName; 209 while (parent.hasChild(name)) 210 { 211 name = originalName + " (" + index + ")"; 212 index++; 213 } 214 215 JCRCalendar calendar = parent.createChild(name, JCRCalendarFactory.CALENDAR_NODETYPE); 216 calendar.setWorkflowName(workflowName); 217 calendar.setDescription(description); 218 calendar.setTemplateDescription(templateDesc); 219 calendar.setColor(color); 220 calendar.setVisibility(StringUtils.isNotEmpty(visibility) ? CalendarVisibility.valueOf(visibility.toUpperCase()) : CalendarVisibility.PRIVATE); 221 parent.saveChanges(); 222 223 // Notify listeners 224 Map<String, Object> eventParams = new HashMap<>(); 225 eventParams.put(ObservationConstants.ARGS_ID, calendar.getId()); 226 eventParams.put(ObservationConstants.ARGS_PARENT_ID, parent.getId()); 227 eventParams.put(ObservationConstants.ARGS_NAME, name); 228 eventParams.put(ObservationConstants.ARGS_PATH, calendar.getPath()); 229 230 if (notify) 231 { 232 _observationManager.notify(new Event(org.ametys.plugins.workspaces.calendars.ObservationConstants.EVENT_CALENDAR_CREATED, _currentUserProvider.getUser(), eventParams)); 233 } 234 235 return getCalendarProperties(calendar); 236 } 237 238 /** 239 * Edit a calendar 240 * @param id The identifier of the calendar 241 * @param inputName The new name 242 * @param templateDesc The new calendar template description 243 * @param color The calendar color 244 * @param isPublic true if the calendar is public 245 * @return The result map with id and name keys 246 * @throws IllegalAccessException If the user has no sufficient rights 247 */ 248 @Callable 249 public Map<String, Object> editCalendar(String id, String inputName, String templateDesc, String color, boolean isPublic) throws IllegalAccessException 250 { 251 CalendarVisibility visibility = isPublic ? CalendarVisibility.PUBLIC : CalendarVisibility.PRIVATE; 252 253 assert id != null; 254 String rename = Text.escapeIllegalJcrChars(inputName); 255 256 AmetysObject object = _resolver.resolveById(id); 257 if (!(object instanceof ModifiableCalendar)) 258 { 259 throw new IllegalClassException(ModifiableCalendar.class, object.getClass()); 260 } 261 262 ModifiableCalendar calendar = (ModifiableCalendar) object; 263 264 // Check user right 265 _explorerResourcesDAO.checkUserRight(object, RIGHTS_CALENDAR_EDIT); 266 267 String name = calendar.getName(); 268 ModifiableTraversableAmetysObject parent = calendar.getParent(); 269 270 if (!StringUtils.equals(rename, name) && parent.hasChild(rename)) 271 { 272 getLogger().warn("Cannot edit the calendar with the new name '" + inputName + "', an object with same name already exists."); 273 return Map.of("message", "already-exist"); 274 } 275 276 if (!_explorerResourcesDAO.checkLock(calendar)) 277 { 278 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to modify calendar '" + object.getName() + "' but it is locked by another user"); 279 return Map.of("message", "locked"); 280 } 281 282 if (!StringUtils.equals(name, rename)) 283 { 284 int index = 2; 285 name = Text.escapeIllegalJcrChars(rename); 286 while (parent.hasChild(name)) 287 { 288 name = rename + " (" + index + ")"; 289 index++; 290 } 291 calendar.rename(name); 292 } 293 294 calendar.setTemplateDescription(templateDesc); 295 calendar.setColor(color); 296 calendar.setVisibility(visibility); 297 298 parent.saveChanges(); 299 300 // Notify listeners 301 Map<String, Object> eventParams = new HashMap<>(); 302 eventParams.put(ObservationConstants.ARGS_ID, calendar.getId()); 303 eventParams.put(ObservationConstants.ARGS_PARENT_ID, parent.getId()); 304 eventParams.put(ObservationConstants.ARGS_NAME, name); 305 eventParams.put(ObservationConstants.ARGS_PATH, calendar.getPath()); 306 307 _observationManager.notify(new Event(org.ametys.plugins.workspaces.calendars.ObservationConstants.EVENT_CALENDAR_UPDATED, _currentUserProvider.getUser(), eventParams)); 308 309 return getCalendarProperties(calendar); 310 } 311 312 /** 313 * Delete a calendar 314 * @param id The id of the calendar 315 * @return The result map with id, parent id and message keys 316 * @throws IllegalAccessException If the user has no sufficient rights 317 */ 318 @Callable 319 public Map<String, Object> deleteCalendar(String id) throws IllegalAccessException 320 { 321 Map<String, Object> result = new HashMap<>(); 322 323 assert id != null; 324 325 AmetysObject object = _resolver.resolveById(id); 326 if (!(object instanceof ModifiableCalendar)) 327 { 328 throw new IllegalClassException(ModifiableCalendar.class, object.getClass()); 329 } 330 331 ModifiableCalendar calendar = (ModifiableCalendar) object; 332 333 // Check user right 334 _explorerResourcesDAO.checkUserRight(object, RIGHTS_CALENDAR_DELETE); 335 336 if (!_explorerResourcesDAO.checkLock(calendar)) 337 { 338 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to delete calendar'" + object.getName() + "' but it is locked by another user"); 339 result.put("message", "locked"); 340 return result; 341 } 342 343 ModifiableExplorerNode parent = calendar.getParent(); 344 String parentId = parent.getId(); 345 String name = calendar.getName(); 346 String path = calendar.getPath(); 347 348 calendar.remove(); 349 parent.saveChanges(); 350 351 // Notify listeners 352 Map<String, Object> eventParams = new HashMap<>(); 353 eventParams.put(ObservationConstants.ARGS_ID, id); 354 eventParams.put(ObservationConstants.ARGS_PARENT_ID, parentId); 355 eventParams.put(ObservationConstants.ARGS_NAME, name); 356 eventParams.put(ObservationConstants.ARGS_PATH, path); 357 358 _observationManager.notify(new Event(org.ametys.plugins.workspaces.calendars.ObservationConstants.EVENT_CALENDAR_DELETED, _currentUserProvider.getUser(), eventParams)); 359 360 result.put("id", id); 361 result.put("parentId", parentId); 362 363 return result; 364 } 365 366 /** 367 * Get or create the calendar ICS token 368 * @param calendar The calendar 369 * @param createIfNotExisting Create the token if none exists for the given calendar 370 * @return The token 371 */ 372 public String getCalendarIcsToken(Calendar calendar, boolean createIfNotExisting) 373 { 374 String token = calendar.getIcsUrlToken(); 375 376 if (createIfNotExisting && token == null && calendar instanceof ModifiableCalendar) 377 { 378 token = UUID.randomUUID().toString(); 379 ((ModifiableCalendar) calendar).setIcsUrlToken(token); 380 ((ModifiableCalendar) calendar).saveChanges(); 381 } 382 383 return token; 384 } 385 386 /** 387 * Retrieve the calendar for the matching ICS token 388 * @param token The ICS token 389 * @return The calendar, or null if not found 390 */ 391 public Calendar getCalendarFromIcsToken(String token) 392 { 393 if (StringUtils.isEmpty(token)) 394 { 395 return null; 396 } 397 398 Expression expr = new StringExpression(JCRCalendar.CALENDAR_ICS_TOKEN, Operator.EQ, token); 399 String calendarsQuery = QueryHelper.getXPathQuery(null, JCRCalendarFactory.CALENDAR_NODETYPE, expr); 400 AmetysObjectIterable<Calendar> calendars = _resolver.query(calendarsQuery); 401 AmetysObjectIterator<Calendar> calendarsIterator = calendars.iterator(); 402 403 if (calendarsIterator.getSize() > 0) 404 { 405 return calendarsIterator.next(); 406 } 407 408 return null; 409 } 410 411 /** 412 * Internal method to extract the data concerning the right of the current user for a calendar 413 * @param calendar The calendar 414 * @return The map of right data. Keys are the rights id, and values indicates whether the current user has the right or not. 415 */ 416 protected Map<String, Object> _extractCalendarRightData(Calendar calendar) 417 { 418 Map<String, Object> rightsData = new HashMap<>(); 419 UserIdentity user = _currentUserProvider.getUser(); 420 421 // Add 422 rightsData.put("add-event", _rightManager.hasRight(user, RIGHTS_EVENT_ADD, calendar) == RightResult.RIGHT_ALLOW); 423 424 // edit - delete 425 rightsData.put("edit", _rightManager.hasRight(user, RIGHTS_CALENDAR_EDIT, calendar) == RightResult.RIGHT_ALLOW); 426 rightsData.put("delete", _rightManager.hasRight(user, RIGHTS_CALENDAR_DELETE, calendar) == RightResult.RIGHT_ALLOW); 427 428 return rightsData; 429 } 430 431 /** 432 * Get the data of every available calendar for the current project 433 * @return the list of calendars 434 */ 435 @Callable 436 public List<Map<String, Object>> getCalendars() 437 { 438 List<Map<String, Object>> calendarsData = new ArrayList<>(); 439 CalendarWorkspaceModule calendarModule = (CalendarWorkspaceModule) _workspaceModuleEP.getModule(CalendarWorkspaceModule.CALENDAR_MODULE_ID); 440 441 AmetysObjectIterable<Calendar> calendars = calendarModule.getCalendars(_getProject()); 442 if (calendars != null) 443 { 444 for (Calendar calendar : calendars) 445 { 446 if (calendarModule.canView(calendar)) 447 { 448 calendarsData.add(this.getCalendarProperties(calendar)); 449 } 450 } 451 } 452 453 return calendarsData; 454 } 455 456 /** 457 * Get the colors of calendars 458 * @return colors 459 */ 460 @Callable 461 public Map<String, CalendarColorsComponent.CalendarColor> getColors() 462 { 463 return _calendarColors.getColors(); 464 } 465 466 /** 467 * Get user rights from project name 468 * @return the user rights 469 */ 470 @Callable 471 public Map<String, Object> getUserRights() 472 { 473 Map<String, Object> results = new HashMap<>(); 474 ModifiableTraversableAmetysObject calendarRoot = _getCalendarRoot(); 475 476 UserIdentity user = _currentUserProvider.getUser(); 477 results.put("canCreateCalendar", _rightManager.hasRight(user, RIGHTS_CALENDAR_ADD, calendarRoot) == RightResult.RIGHT_ALLOW); 478 results.put("canEditCalendar", _rightManager.hasRight(user, RIGHTS_CALENDAR_EDIT, calendarRoot) == RightResult.RIGHT_ALLOW); 479 results.put("canRemoveCalendar", _rightManager.hasRight(user, RIGHTS_CALENDAR_DELETE, calendarRoot) == RightResult.RIGHT_ALLOW); 480 results.put("canCreateEvent", _rightManager.hasRight(user, RIGHTS_EVENT_ADD, calendarRoot) == RightResult.RIGHT_ALLOW); 481 results.put("canEditEvent", _rightManager.hasRight(user, RIGHTS_EVENT_EDIT, calendarRoot) == RightResult.RIGHT_ALLOW); 482 results.put("canRemoveAnyEvent", _rightManager.hasRight(user, RIGHTS_EVENT_DELETE, calendarRoot) == RightResult.RIGHT_ALLOW); 483 results.put("canRemoveSelfEvent", _rightManager.hasRight(user, RIGHTS_EVENT_DELETE_OWN, calendarRoot) == RightResult.RIGHT_ALLOW); 484 results.put("canCreateTags", _rightManager.hasRight(user, ProjectConstants.RIGHT_PROJECT_ADD_TAG, calendarRoot) == RightResult.RIGHT_ALLOW); 485 results.put("canHandleResource", _rightManager.hasRight(user, RIGHTS_HANDLE_RESOURCE, calendarRoot) == RightResult.RIGHT_ALLOW); 486 results.put("canBookResource", _rightManager.hasRight(user, RIGHTS_BOOK_RESOURCE, calendarRoot) == RightResult.RIGHT_ALLOW); 487 results.put("sharePrivateCalendar", _rightManager.hasRight(user, RIGHTS_EVENT_EDIT, calendarRoot) == RightResult.RIGHT_ALLOW); 488 489 return results; 490 } 491 492 /** 493 * Get the calendar root 494 * @return the calendar root 495 */ 496 protected ModifiableTraversableAmetysObject _getCalendarRoot() 497 { 498 CalendarWorkspaceModule calendarModule = (CalendarWorkspaceModule) _workspaceModuleEP.getModule(CalendarWorkspaceModule.CALENDAR_MODULE_ID); 499 return calendarModule.getCalendarsRoot(_getProject(), true); 500 } 501 502 /** 503 * Get the data of calendar used to store resources 504 * @return the calendar used to store resources 505 */ 506 @Callable 507 public Map<String, Object> getResourceCalendar() 508 { 509 CalendarWorkspaceModule calendarModule = (CalendarWorkspaceModule) _workspaceModuleEP.getModule(CalendarWorkspaceModule.CALENDAR_MODULE_ID); 510 511 Calendar calendar = calendarModule.getResourceCalendar(_getProject()); 512 return this.getCalendarProperties(calendar); 513 } 514}