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