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