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.explorer.calendars.actions; 017 018import java.time.ZonedDateTime; 019import java.time.format.DateTimeFormatter; 020import java.util.ArrayList; 021import java.util.Date; 022import java.util.GregorianCalendar; 023import java.util.HashMap; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028 029import javax.jcr.RepositoryException; 030 031import org.apache.avalon.framework.component.Component; 032import org.apache.avalon.framework.logger.AbstractLogEnabled; 033import org.apache.avalon.framework.service.ServiceException; 034import org.apache.avalon.framework.service.ServiceManager; 035import org.apache.avalon.framework.service.Serviceable; 036import org.apache.commons.lang.BooleanUtils; 037import org.apache.commons.lang.IllegalClassException; 038import org.apache.commons.lang3.StringUtils; 039import org.apache.jackrabbit.util.Text; 040 041import org.ametys.core.observation.Event; 042import org.ametys.core.observation.ObservationManager; 043import org.ametys.core.right.RightManager; 044import org.ametys.core.right.RightManager.RightResult; 045import org.ametys.core.ui.Callable; 046import org.ametys.core.user.CurrentUserProvider; 047import org.ametys.core.user.User; 048import org.ametys.core.user.UserIdentity; 049import org.ametys.core.user.UserManager; 050import org.ametys.core.util.DateUtils; 051import org.ametys.plugins.explorer.ExplorerNode; 052import org.ametys.plugins.explorer.ModifiableExplorerNode; 053import org.ametys.plugins.explorer.ObservationConstants; 054import org.ametys.plugins.explorer.calendars.Calendar; 055import org.ametys.plugins.explorer.calendars.Calendar.CalendarVisibility; 056import org.ametys.plugins.explorer.calendars.CalendarEvent; 057import org.ametys.plugins.explorer.calendars.ModifiableCalendar; 058import org.ametys.plugins.explorer.calendars.ModifiableCalendarEvent; 059import org.ametys.plugins.explorer.calendars.jcr.JCRCalendar; 060import org.ametys.plugins.explorer.calendars.jcr.JCRCalendarEvent; 061import org.ametys.plugins.explorer.calendars.jcr.JCRCalendarFactory; 062import org.ametys.plugins.explorer.resources.ModifiableResourceCollection; 063import org.ametys.plugins.explorer.resources.actions.ExplorerResourcesDAO; 064import org.ametys.plugins.explorer.workflow.AbstractExplorerNodeWorkflowComponent; 065import org.ametys.plugins.repository.AmetysObject; 066import org.ametys.plugins.repository.AmetysObjectIterable; 067import org.ametys.plugins.repository.AmetysObjectResolver; 068import org.ametys.plugins.repository.AmetysRepositoryException; 069import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 070import org.ametys.plugins.workflow.support.WorkflowHelper; 071import org.ametys.plugins.workflow.support.WorkflowProvider; 072import org.ametys.runtime.i18n.I18nizableText; 073 074import com.opensymphony.workflow.Workflow; 075import com.opensymphony.workflow.WorkflowException; 076import com.opensymphony.workflow.spi.Step; 077 078/** 079 * Calendar DAO 080 */ 081public class CalendarDAO extends AbstractLogEnabled implements Serviceable, Component 082{ 083 /** Avalon Role */ 084 public static final String ROLE = CalendarDAO.class.getName(); 085 086 /** Right to add a calendar */ 087 public static final String RIGHTS_CALENDAR_ADD = "Plugin_Explorer_Calendar_Add"; 088 /** Right to edit a calendar */ 089 public static final String RIGHTS_CALENDAR_EDIT = "Plugin_Explorer_Calendar_Edit"; 090 /** Right to delete a calendar */ 091 public static final String RIGHTS_CALENDAR_DELETE = "Plugin_Explorer_Calendar_Delete"; 092 /** Right to add a event */ 093 public static final String RIGHTS_EVENT_ADD = "Plugin_Explorer_Event_Add"; 094 /** Right to edit a event */ 095 public static final String RIGHTS_EVENT_EDIT = "Plugin_Explorer_Event_Edit"; 096 /** Right to propose a event */ 097 public static final String RIGHTS_EVENT_PROPOSE = "Plugin_Explorer_Event_Propose"; 098 /** Right to validate a event */ 099 public static final String RIGHTS_EVENT_VALIDATE = "Plugin_Explorer_Event_Validate"; 100 /** Right to refuse a event */ 101 public static final String RIGHTS_EVENT_REFUSE = "Plugin_Explorer_Event_Refuse"; 102 /** Right to delete a event */ 103 public static final String RIGHTS_EVENT_DELETE = "Plugin_Explorer_Event_Delete"; 104 /** Right to delete_own a event */ 105 public static final String RIGHTS_EVENT_DELETE_OWN = "Plugin_Explorer_Owned_Event_Delete"; 106 107 /** Explorer resources DAO */ 108 protected ExplorerResourcesDAO _explorerResourcesDAO; 109 110 /** Ametys resolver */ 111 protected AmetysObjectResolver _resolver; 112 113 /** Observer manager. */ 114 protected ObservationManager _observationManager; 115 116 /** The current user provider. */ 117 protected CurrentUserProvider _currentUserProvider; 118 119 /** The rights manager */ 120 protected RightManager _rightManager; 121 122 /** User manager */ 123 protected UserManager _userManager; 124 125 /** The workflow provider */ 126 protected WorkflowProvider _workflowProvider; 127 128 /** The workflow helper */ 129 protected WorkflowHelper _workflowHelper; 130 131 132 public void service(ServiceManager manager) throws ServiceException 133 { 134 _explorerResourcesDAO = (ExplorerResourcesDAO) manager.lookup(ExplorerResourcesDAO.ROLE); 135 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 136 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 137 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 138 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 139 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 140 _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE); 141 _workflowHelper = (WorkflowHelper) manager.lookup(WorkflowHelper.ROLE); 142 } 143 144 /** 145 * Get calendar info 146 * @param id The calendar id 147 * @param recursive True to get data for sub calendars 148 * @param includeEvents True to also include child events 149 * @return the calendar data in a map 150 */ 151 @Callable 152 public Map<String, Object> getCalendarData(String id, boolean recursive, boolean includeEvents) 153 { 154 Calendar calendar = (Calendar) _resolver.resolveById(id); 155 return getCalendarData(calendar, recursive, includeEvents); 156 } 157 158 /** 159 * Get calendar info 160 * @param calendar The calendar 161 * @param recursive True to get data for sub calendars 162 * @param includeEvents True to also include child events 163 * @return the calendar data in a map 164 */ 165 public Map<String, Object> getCalendarData(Calendar calendar, boolean recursive, boolean includeEvents) 166 { 167 Map<String, Object> result = new HashMap<>(); 168 169 result.put("id", calendar.getId()); 170 result.put("title", Text.unescapeIllegalJcrChars(calendar.getName())); 171 result.put("description", calendar.getDescription()); 172 result.put("templateDesc", calendar.getTemplateDescription()); 173 result.put("color", calendar.getColor()); 174 result.put("visibility", calendar.getVisibility().name().toLowerCase()); 175 result.put("workflowName", calendar.getWorkflowName()); 176 177 if (recursive) 178 { 179 List<Map<String, Object>> calendarList = new LinkedList<>(); 180 result.put("calendars", calendarList); 181 182 AmetysObjectIterable<AmetysObject> children = calendar.getChildren(); 183 for (AmetysObject child : children) 184 { 185 if (child instanceof Calendar) 186 { 187 calendarList.add(getCalendarData((Calendar) child, recursive, includeEvents)); 188 } 189 } 190 } 191 192 if (includeEvents) 193 { 194 List<Map<String, Object>> eventList = new LinkedList<>(); 195 result.put("events", eventList); 196 197 AmetysObjectIterable<AmetysObject> children = calendar.getChildren(); 198 for (AmetysObject child : children) 199 { 200 if (child instanceof CalendarEvent) 201 { 202 eventList.add(getEventData((CalendarEvent) child, false)); 203 } 204 } 205 } 206 207 return result; 208 } 209 210 /** 211 * Get the template description of a calendar 212 * @param calendarId The identifier of the calendar 213 * @return The template description 214 */ 215 @Callable 216 public String getTemplateDescription(String calendarId) 217 { 218 Calendar calendar = _resolver.resolveById(calendarId); 219 return StringUtils.defaultString(calendar.getTemplateDescription()); 220 } 221 222 /** 223 * Get event info 224 * @param ids The event ids 225 * @param fullInfo true to include full info (rights, parent id, etc...) 226 * @return the list of event data 227 */ 228 @Callable 229 public List<Map<String, Object>> getEventsDataByIds(List<String> ids, boolean fullInfo) 230 { 231 List<CalendarEvent> events = new LinkedList<>(); 232 for (String id : ids) 233 { 234 events.add((CalendarEvent) _resolver.resolveById(id)); 235 } 236 237 return getEventsData(events, fullInfo); 238 } 239 240 /** 241 * Get event info 242 * @param events The events 243 * @param fullInfo true to include full info (rights, parent id, etc...) 244 * @return the list of event data 245 */ 246 public List<Map<String, Object>> getEventsData(List<CalendarEvent> events, boolean fullInfo) 247 { 248 List<Map<String, Object>> result = new LinkedList<>(); 249 250 for (CalendarEvent event : events) 251 { 252 result.add(getEventData(event, fullInfo)); 253 } 254 255 return result; 256 } 257 258 /** 259 * Get event info 260 * @param id The event id 261 * @param fullInfo true to include full info (rights, parent id, etc...) 262 * @return the event data in a map 263 */ 264 @Callable 265 public Map<String, Object> getEventDataById(String id, boolean fullInfo) 266 { 267 CalendarEvent event = (CalendarEvent) _resolver.resolveById(id); 268 return getEventData(event, fullInfo); 269 } 270 271 /** 272 * Get event info for a specific occurrence 273 * @param id The event id 274 * @param occurrence a string representing the occurrence date (ISO format). 275 * @param fullInfo true to include full info (rights, parent id, etc...) 276 * @return the event data in a map 277 */ 278 @Callable 279 public Map<String, Object> getEventDataById(String id, String occurrence, boolean fullInfo) 280 { 281 CalendarEvent event = (CalendarEvent) _resolver.resolveById(id); 282 Date occurrenceDate = DateUtils.parse(occurrence); 283 284 return getEventData(event, occurrenceDate, fullInfo); 285 } 286 287 /** 288 * Get event info for a specific occurrence 289 * @param event The event 290 * @param occurrenceDate the occurrence 291 * @param fullInfo true to include full info (rights, parent id, etc...) 292 * @return the event data in a map 293 */ 294 public Map<String, Object> getEventData(CalendarEvent event, Date occurrenceDate, boolean fullInfo) 295 { 296 Map<String, Object> eventData = getEventData(event, fullInfo); 297 298 if (occurrenceDate != null) 299 { 300 // replace id, start and end date with specific occurrence data 301 eventData.putAll(getEventOccurrenceData(event, occurrenceDate)); 302 } 303 304 return eventData; 305 } 306 307 /** 308 * Get event info 309 * @param event The event 310 * @param fullInfo true to include full info (rights, parent id, etc...) 311 * @return the event data in a map 312 */ 313 public Map<String, Object> getEventData(CalendarEvent event, boolean fullInfo) 314 { 315 Calendar calendar = event.getParent(); 316 Map<String, Object> result = new HashMap<>(); 317 318 result.put("id", event.getId()); 319 result.put("calendarId", event.getParent().getId()); 320 result.put("color", calendar.getColor()); 321 322 result.put("title", event.getTitle()); 323 result.put("description", event.getDescription()); 324 result.put("fullDay", event.getFullDay()); 325 result.put("recurrenceType", event.getRecurrenceType().toString()); 326 327 result.put("location", event.getLocation()); 328 result.put("keywords", event.getKeywords()); 329 330 Date untilDate = event.getRepeatUntil(); 331 if (untilDate != null) 332 { 333 result.put("untilDate", DateUtils.dateToString(untilDate)); 334 } 335 336 Date startDateEvent = event.getStartDate(); 337 Date endDateEvent = event.getEndDate(); 338 339 if (event.getFullDay()) 340 { 341 GregorianCalendar gcStart = new GregorianCalendar(); 342 gcStart.setTime(startDateEvent); 343 gcStart.set(java.util.Calendar.HOUR, 0); 344 gcStart.set(java.util.Calendar.MINUTE, 0); 345 gcStart.set(java.util.Calendar.SECOND, 0); 346 gcStart.set(java.util.Calendar.MILLISECOND, 0); 347 startDateEvent = gcStart.getTime(); 348 349 GregorianCalendar gcEnd = new GregorianCalendar(); 350 gcEnd.setTime(endDateEvent); 351 gcEnd.set(java.util.Calendar.HOUR, 23); 352 gcEnd.set(java.util.Calendar.MINUTE, 59); 353 gcEnd.set(java.util.Calendar.SECOND, 59); 354 gcEnd.set(java.util.Calendar.MILLISECOND, 999); 355 endDateEvent = gcEnd.getTime(); 356 357 GregorianCalendar gcEndPlus1 = new GregorianCalendar(); 358 gcEndPlus1.setTime(endDateEvent); 359 gcEndPlus1.add(java.util.Calendar.DAY_OF_YEAR, 1); 360 gcEndPlus1.set(java.util.Calendar.HOUR, 0); 361 gcEndPlus1.set(java.util.Calendar.MINUTE, 0); 362 gcEndPlus1.set(java.util.Calendar.SECOND, 0); 363 gcEndPlus1.set(java.util.Calendar.MILLISECOND, 0); 364 365 long milis = gcEndPlus1.getTimeInMillis() - gcStart.getTimeInMillis(); 366 Integer nbDays = Math.round(milis / (1000 * 60 * 60 * 24)); 367 368 result.put("nbDays", nbDays.intValue()); 369 result.put("endDateNextDay", DateUtils.dateToString(gcEndPlus1.getTime())); 370 } 371 372 result.put("startDate", DateUtils.dateToString(startDateEvent)); 373 result.put("endDate", DateUtils.dateToString(endDateEvent)); 374 375 //excluded occurences 376 List<Date> excludedOccurences = event.getExcludedOccurences(); 377 if (excludedOccurences != null && !excludedOccurences.isEmpty()) 378 { 379 List<String> excludedOccurencesStrings = new ArrayList<>(); 380 for (Date excludedOccurence : excludedOccurences) 381 { 382 excludedOccurencesStrings.add(DateUtils.dateToString(excludedOccurence)); 383 } 384 result.put("excludedDates", excludedOccurencesStrings); 385 } 386 387 // creator 388 UserIdentity creatorIdentity = event.getCreator(); 389 User creator = _userManager.getUser(creatorIdentity); 390 391 result.put("creator", creatorIdentity); 392 result.put("creatorFullName", creator != null ? creator.getFullName() : creatorIdentity.getLogin()); 393 result.put("creationDate", DateUtils.dateToString(event.getCreationDate())); 394 395 // last modification 396 UserIdentity contributorIdentity = event.getLastContributor(); 397 User contributor = _userManager.getUser(contributorIdentity); 398 399 result.put("contributor", contributorIdentity); 400 result.put("contributorFullName", contributor != null ? contributor.getFullName() : contributorIdentity.getLogin()); 401 result.put("lastModified", DateUtils.dateToString(event.getLastModified())); 402 403 // last validation 404 UserIdentity validatorIdentity = event.getLastValidator(); 405 406 if (validatorIdentity != null) 407 { 408 result.put("validator", validatorIdentity); 409 User validator = _userManager.getUser(validatorIdentity); 410 result.put("validatorFullName", validator != null ? validator.getFullName() : validatorIdentity.getLogin()); 411 } 412 413 Date lastValidated = event.getLastValidated(); 414 if (lastValidated != null) 415 { 416 result.put("lastValidated", DateUtils.dateToString(lastValidated)); 417 } 418 419 Workflow workflow = _workflowProvider.getAmetysObjectWorkflow(event); 420 long workflowId = event.getWorkflowId(); 421 result.put("workflowId", workflowId); 422 result.put("workflowName", workflow.getWorkflowName(workflowId)); 423 424 List<Step> listSteps = workflow.getCurrentSteps(event.getWorkflowId()); 425 if (!listSteps.isEmpty()) 426 { 427 // We select the first current step 428 Step step = listSteps.get(0); 429 String stepName = _workflowHelper.getStepName(calendar.getWorkflowName(), step.getStepId()); 430 String[] nameParts = stepName.split(":"); 431 432 result.put("stepId", step.getStepId()); 433 result.put("stepName", nameParts[nameParts.length - 1]); // step name without the possible catalog name 434 435 I18nizableText workflowStepName = new I18nizableText("application", stepName); 436 437 if ("application".equals(workflowStepName.getCatalogue())) 438 { 439 result.put("icon-small-workflow", "/plugins/explorer/resources_workflow/" + workflowStepName.getKey() + "-small.png"); 440 } 441 else 442 { 443 String pluginName = workflowStepName.getCatalogue().substring("plugin.".length()); 444 result.put("icon-small-workflow", "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + "-small.png"); 445 } 446 } 447 448 if (fullInfo) 449 { 450 result.putAll(_getEventDataFullInfo(event)); 451 } 452 453 return result; 454 } 455 456 /** 457 * Retrieves the event additional info (rights, parent id, etc...) 458 * @param event The event 459 * @return the event additional info (rights, parent id, etc...) in a map 460 */ 461 protected Map<String, Object> _getEventDataFullInfo(CalendarEvent event) 462 { 463 Map<String, Object> result = new HashMap<>(); 464 465 ExplorerNode explorerNode = event.getParent(); 466 ExplorerNode root = explorerNode; 467 while (true) 468 { 469 if (root.getParent() instanceof ExplorerNode) 470 { 471 root = root.getParent(); 472 } 473 else 474 { 475 break; 476 } 477 } 478 result.put("rootId", root.getId()); 479 result.put("parentId", explorerNode.getId()); 480 result.put("name", event.getName()); 481 result.put("path", explorerNode.getExplorerPath()); 482 result.put("isModifiable", true); 483 484 result.put("rights", _getUserRights(explorerNode)); 485 486 return result; 487 } 488 489 /** 490 * Get the user rights on the resource collection 491 * @param node The explorer node 492 * @return The user's rights 493 */ 494 protected Set<String> _getUserRights(ExplorerNode node) 495 { 496 return _rightManager.getUserRights(_currentUserProvider.getUser(), node); 497 } 498 499 /** 500 * Get info about the occurrence of an event 501 * @param event The event 502 * @param date The start date of the occurrence of the event 503 * @return the event occurrence data in a map 504 */ 505 public Map<String, Object> getEventOccurrenceData(CalendarEvent event, Date date) 506 { 507 Map<String, Object> result = new HashMap<>(); 508 509 String occurrenceDateIso = DateUtils.dateToString(date); 510 result.put("id", event.getId() + "$" + occurrenceDateIso); 511 result.put("occurrenceDate", occurrenceDateIso); 512 513 Date firstStartDateEvent = event.getStartDate(); 514 Date firstEndDateEvent = event.getEndDate(); 515 516 long diff = firstEndDateEvent.getTime() - firstStartDateEvent.getTime(); 517 Date startDateEvent = date; 518 Date endDateEvent = new Date(date.getTime() + diff); 519 520 if (event.getFullDay()) 521 { 522 GregorianCalendar gcStart = new GregorianCalendar(); 523 gcStart.setTime(startDateEvent); 524 gcStart.set(java.util.Calendar.HOUR_OF_DAY, 0); 525 gcStart.set(java.util.Calendar.MINUTE, 0); 526 gcStart.set(java.util.Calendar.SECOND, 0); 527 gcStart.set(java.util.Calendar.MILLISECOND, 0); 528 529 startDateEvent = gcStart.getTime(); 530 531 GregorianCalendar gcEnd = new GregorianCalendar(); 532 gcEnd.setTime(endDateEvent); 533 gcEnd.set(java.util.Calendar.HOUR_OF_DAY, 23); 534 gcEnd.set(java.util.Calendar.MINUTE, 59); 535 gcEnd.set(java.util.Calendar.SECOND, 59); 536 gcEnd.set(java.util.Calendar.MILLISECOND, 999); 537 538 endDateEvent = gcEnd.getTime(); 539 } 540 541 result.put("startDate", DateUtils.dateToString(startDateEvent)); 542 result.put("endDate", DateUtils.dateToString(endDateEvent)); 543 544 return result; 545 } 546 547 /** 548 * Add a calendar 549 * @param id The identifier of the parent in which the calendar will be added 550 * @param inputName The desired name for the calendar 551 * @param description The calendar description 552 * @param templateDesc The calendar template description 553 * @param color The calendar color 554 * @param visibility The calendar visibility 555 * @param workflowName The calendar workflow name 556 * @param renameIfExists True to rename if existing 557 * @return The result map with id, parentId and name keys 558 * @throws IllegalAccessException If the user has no sufficient rights 559 */ 560 @Callable 561 public Map<String, Object> addCalendar(String id, String inputName, String description, String templateDesc, String color, String visibility, String workflowName, Boolean renameIfExists) throws IllegalAccessException 562 { 563 Map<String, Object> result = new HashMap<>(); 564 565 String originalName = Text.escapeIllegalJcrChars(inputName); 566 assert id != null; 567 568 AmetysObject object = _resolver.resolveById(id); 569 if (!(object instanceof ModifiableResourceCollection || object instanceof Calendar)) 570 { 571 throw new IllegalClassException(ModifiableResourceCollection.class, object.getClass()); 572 } 573 574 ModifiableTraversableAmetysObject parent = (ModifiableTraversableAmetysObject) object; 575 576 // Check user right 577 _explorerResourcesDAO.checkUserRight(object, RIGHTS_CALENDAR_ADD); 578 579 if (BooleanUtils.isNotTrue(renameIfExists) && parent.hasChild(originalName)) 580 { 581 getLogger().warn("Cannot create the calendar with name '" + originalName + "', an object with same name already exists."); 582 result.put("message", "already-exist"); 583 return result; 584 } 585 586 if (!_explorerResourcesDAO.checkLock(parent)) 587 { 588 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to modify the object '" + object.getName() + "' but it is locked by another user"); 589 result.put("message", "locked"); 590 return result; 591 } 592 593 int index = 2; 594 String name = originalName; 595 while (parent.hasChild(name)) 596 { 597 name = originalName + " (" + index + ")"; 598 index++; 599 } 600 601 JCRCalendar calendar = parent.createChild(name, JCRCalendarFactory.CALENDAR_NODETYPE); 602 calendar.setWorkflowName(workflowName); 603 calendar.setDescription(description); 604 calendar.setTemplateDescription(templateDesc); 605 calendar.setColor(color); 606 calendar.setVisibility(StringUtils.isNotEmpty(visibility) ? CalendarVisibility.valueOf(visibility.toUpperCase()) : CalendarVisibility.PRIVATE); 607 parent.saveChanges(); 608 609 // Notify listeners 610 Map<String, Object> eventParams = new HashMap<>(); 611 eventParams.put(ObservationConstants.ARGS_ID, calendar.getId()); 612 eventParams.put(ObservationConstants.ARGS_PARENT_ID, id); 613 eventParams.put(ObservationConstants.ARGS_NAME, name); 614 eventParams.put(ObservationConstants.ARGS_PATH, calendar.getPath()); 615 616 _observationManager.notify(new Event(ObservationConstants.EVENT_CALENDAR_CREATED, _currentUserProvider.getUser(), eventParams)); 617 618 result.put("id", calendar.getId()); 619 result.put("parentId", id); 620 result.put("name", Text.unescapeIllegalJcrChars(name)); 621 622 return result; 623 } 624 625 /** 626 * Edit a calendar 627 * @param id The identifier of the calendar 628 * @param inputName The new name 629 * @param description The new description 630 * @param templateDesc The new calendar template description 631 * @param color The calendar color 632 * @param visibility The calendar visibility 633 * @param workflowName The calendar workflow name 634 * @param renameIfExists True to rename if existing 635 * @param fullEdit true to allow full edition, otherwise only the name will be changed 636 * @return The result map with id and name keys 637 * @throws IllegalAccessException If the user has no sufficient rights 638 */ 639 @Callable 640 public Map<String, Object> editCalendar(String id, String inputName, String description, String templateDesc, String color, String visibility, String workflowName, Boolean renameIfExists, Boolean fullEdit) throws IllegalAccessException 641 { 642 // TODO handle template desc 643 Map<String, Object> result = new HashMap<>(); 644 645 assert id != null; 646 String rename = Text.escapeIllegalJcrChars(inputName); 647 648 AmetysObject object = _resolver.resolveById(id); 649 if (!(object instanceof ModifiableCalendar)) 650 { 651 throw new IllegalClassException(ModifiableCalendar.class, object.getClass()); 652 } 653 654 ModifiableCalendar calendar = (ModifiableCalendar) object; 655 656 // Check user right 657 _explorerResourcesDAO.checkUserRight(object, RIGHTS_CALENDAR_EDIT); 658 659 String name = calendar.getName(); 660 ModifiableTraversableAmetysObject parent = calendar.getParent(); 661 662 if (BooleanUtils.isNotTrue(renameIfExists) && !StringUtils.equals(rename, name) && parent.hasChild(rename)) 663 { 664 getLogger().warn("Cannot edit the calendar with the new name '" + inputName + "', an object with same name already exists."); 665 result.put("message", "already-exist"); 666 return result; 667 } 668 669 if (!_explorerResourcesDAO.checkLock(calendar)) 670 { 671 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to modify calendar '" + object.getName() + "' but it is locked by another user"); 672 result.put("message", "locked"); 673 return result; 674 } 675 676 if (!StringUtils.equals(name, rename)) 677 { 678 int index = 2; 679 name = Text.escapeIllegalJcrChars(rename); 680 while (parent.hasChild(name)) 681 { 682 name = rename + " (" + index + ")"; 683 index++; 684 } 685 calendar.rename(name); 686 } 687 688 if (BooleanUtils.isTrue(fullEdit)) 689 { 690 calendar.setDescription(description); 691 calendar.setTemplateDescription(templateDesc); 692 calendar.setColor(color); 693 calendar.setVisibility(StringUtils.isNotEmpty(visibility) ? CalendarVisibility.valueOf(visibility.toUpperCase()) : CalendarVisibility.PRIVATE); 694 } 695 696 parent.saveChanges(); 697 698 // Notify listeners 699 Map<String, Object> eventParams = new HashMap<>(); 700 eventParams.put(ObservationConstants.ARGS_ID, calendar.getId()); 701 eventParams.put(ObservationConstants.ARGS_PARENT_ID, parent.getId()); 702 eventParams.put(ObservationConstants.ARGS_NAME, name); 703 eventParams.put(ObservationConstants.ARGS_PATH, calendar.getPath()); 704 705 _observationManager.notify(new Event(ObservationConstants.EVENT_CALENDAR_UPDATED, _currentUserProvider.getUser(), eventParams)); 706 707 result.put("id", calendar.getId()); 708 result.put("title", Text.unescapeIllegalJcrChars(name)); 709 710 return result; 711 } 712 713 /** 714 * Move a event to another calendar 715 * @param event The event to move 716 * @param parent The new parent calendar 717 * @throws AmetysRepositoryException if an error occurred while moving 718 */ 719 public void move(JCRCalendarEvent event, JCRCalendar parent) throws AmetysRepositoryException 720 { 721 try 722 { 723 event.getNode().getSession().move(event.getNode().getPath(), parent.getNode().getPath() + "/ametys:calendar-event"); 724 725 Workflow workflow = _workflowProvider.getAmetysObjectWorkflow(event); 726 727 String previousWorkflowName = workflow.getWorkflowName(event.getWorkflowId()); 728 String workflowName = parent.getWorkflowName(); 729 730 if (!StringUtils.equals(previousWorkflowName, workflowName)) 731 { 732 // If both calendar have a different workflow, initialize a new workflow instance for the event 733 HashMap<String, Object> inputs = new HashMap<>(); 734 inputs.put(AbstractExplorerNodeWorkflowComponent.EXPLORERNODE_KEY, parent); 735 workflow = _workflowProvider.getAmetysObjectWorkflow(event); 736 737 long workflowId = workflow.initialize(workflowName, 0, inputs); 738 event.setWorkflowId(workflowId); 739 } 740 } 741 catch (WorkflowException | RepositoryException e) 742 { 743 String errorMsg = String.format("Fail to move the event '%s' to the calendar '%s'.", event.getId(), parent.getId()); 744 throw new AmetysRepositoryException(errorMsg, e); 745 } 746 } 747 748 /** 749 * Delete a calendar 750 * @param id The id of the calendar 751 * @return The result map with id, parent id and message keys 752 * @throws IllegalAccessException If the user has no sufficient rights 753 */ 754 @Callable 755 public Map<String, Object> deleteCalendar(String id) throws IllegalAccessException 756 { 757 Map<String, Object> result = new HashMap<>(); 758 759 assert id != null; 760 761 AmetysObject object = _resolver.resolveById(id); 762 if (!(object instanceof ModifiableCalendar)) 763 { 764 throw new IllegalClassException(ModifiableCalendar.class, object.getClass()); 765 } 766 767 ModifiableCalendar calendar = (ModifiableCalendar) object; 768 769 // Check user right 770 _explorerResourcesDAO.checkUserRight(object, RIGHTS_CALENDAR_DELETE); 771 772 if (!_explorerResourcesDAO.checkLock(calendar)) 773 { 774 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to delete calendar'" + object.getName() + "' but it is locked by another user"); 775 result.put("message", "locked"); 776 return result; 777 } 778 779 ModifiableExplorerNode parent = calendar.getParent(); 780 String parentId = parent.getId(); 781 String name = calendar.getName(); 782 String path = calendar.getPath(); 783 784 calendar.remove(); 785 parent.saveChanges(); 786 787 // Notify listeners 788 Map<String, Object> eventParams = new HashMap<>(); 789 eventParams.put(ObservationConstants.ARGS_ID, id); 790 eventParams.put(ObservationConstants.ARGS_PARENT_ID, parentId); 791 eventParams.put(ObservationConstants.ARGS_NAME, name); 792 eventParams.put(ObservationConstants.ARGS_PATH, path); 793 794 _observationManager.notify(new Event(ObservationConstants.EVENT_CALENDAR_DELETED, _currentUserProvider.getUser(), eventParams)); 795 796 result.put("id", id); 797 result.put("parentId", parentId); 798 799 return result; 800 } 801 802 /** 803 * Do an event workflow action 804 * @param parameters The map of action parameters 805 * @return The map of results populated by the workflow action 806 * @throws WorkflowException if an error occurred 807 */ 808 @Callable 809 public Map<String, Object> doWorkflowEventAction(Map<String, Object> parameters) throws WorkflowException 810 { 811 Map<String, Object> result = new HashMap<>(); 812 HashMap<String, Object> inputs = new HashMap<>(); 813 814 inputs.put("parameters", parameters); 815 inputs.put("result", result); 816 817 String eventId = (String) parameters.get("id"); 818 Long workflowInstanceId = null; 819 CalendarEvent event = null; 820 if (StringUtils.isNotEmpty(eventId)) 821 { 822 event = _resolver.resolveById(eventId); 823 workflowInstanceId = event.getWorkflowId(); 824 } 825 826 inputs.put("eventId", eventId); 827 828 Calendar calendar = null; 829 String calendarId = (String) parameters.get("parentId"); 830 831 if (StringUtils.isNotEmpty(calendarId)) 832 { 833 calendar = _resolver.resolveById(calendarId); 834 } 835 // parentId can be not provided for some basic actions where the event already exists 836 else if (event != null) 837 { 838 calendar = event.getParent(); 839 } 840 else 841 { 842 throw new WorkflowException("Unable to retrieve the current calendar"); 843 } 844 845 inputs.put(AbstractExplorerNodeWorkflowComponent.EXPLORERNODE_KEY, calendar); 846 847 String workflowName = calendar.getWorkflowName(); 848 if (workflowName == null) 849 { 850 throw new IllegalArgumentException("The workflow name is not specified"); 851 } 852 853 int actionId = (int) parameters.get("actionId"); 854 855 boolean sendMail = true; 856 String choice = (String) parameters.get("choice"); 857 if (actionId == 2 && "unit".equals(choice)) 858 { 859 sendMail = false; 860 } 861 inputs.put("sendMail", sendMail); 862 863 Workflow workflow = _workflowProvider.getAmetysObjectWorkflow(event != null ? event : null); 864 865 if (workflowInstanceId == null) 866 { 867 try 868 { 869 workflow.initialize(workflowName, actionId, inputs); 870 } 871 catch (WorkflowException e) 872 { 873 getLogger().error("An error occured while creating workflow '" + workflowName + "' with action '" + actionId, e); 874 throw e; 875 } 876 } 877 else 878 { 879 try 880 { 881 workflow.doAction(workflowInstanceId, actionId, inputs); 882 } 883 catch (WorkflowException e) 884 { 885 getLogger().error("An error occured while doing action '" + actionId + "'with the workflow '" + workflowName, e); 886 throw e; 887 } 888 } 889 890 return result; 891 } 892 893 /** 894 * Delete an event 895 * @param id The id of the event 896 * @param occurrence a string representing the occurrence date (ISO format). 897 * @param choice The type of modification 898 * @return The result map with id, parent id and message keys 899 * @throws IllegalAccessException If the user has no sufficient rights 900 */ 901 @Callable 902 public Map<String, Object> deleteEvent(String id, String occurrence, String choice) throws IllegalAccessException 903 { 904 Map<String, Object> result = new HashMap<>(); 905 906 assert id != null; 907 908 AmetysObject object = _resolver.resolveById(id); 909 if (!(object instanceof ModifiableCalendarEvent)) 910 { 911 throw new IllegalClassException(ModifiableCalendarEvent.class, object.getClass()); 912 } 913 914 ModifiableCalendarEvent event = (ModifiableCalendarEvent) object; 915 ModifiableCalendar calendar = event.getParent(); 916 917 // Check user right 918 try 919 { 920 _explorerResourcesDAO.checkUserRight(calendar, RIGHTS_EVENT_DELETE); 921 } 922 catch (IllegalAccessException e) 923 { 924 UserIdentity user = _currentUserProvider.getUser(); 925 UserIdentity creator = event.getCreator(); 926 RightResult rightCreator = _rightManager.hasRight(user, RIGHTS_EVENT_DELETE_OWN, calendar); 927 boolean hasOwnDeleteRight = rightCreator == RightResult.RIGHT_ALLOW && creator.equals(user); 928 if (!hasOwnDeleteRight) 929 { 930 // rethrow exception 931 throw e; 932 } 933 } 934 935 if (!_explorerResourcesDAO.checkLock(event)) 936 { 937 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to delete event'" + object.getName() + "' but it is locked by another user"); 938 result.put("message", "locked"); 939 return result; 940 } 941 942 String parentId = calendar.getId(); 943 String name = event.getName(); 944 String path = event.getPath(); 945 946 if (StringUtils.isNotBlank(choice) && choice.equals("unit")) 947 { 948 ArrayList<Date> excludedOccurrences = new ArrayList<>(); 949 excludedOccurrences.addAll(event.getExcludedOccurences()); 950 long date = ZonedDateTime.parse(occurrence, DateTimeFormatter.ISO_DATE_TIME).toInstant().toEpochMilli(); 951 GregorianCalendar gCalendar = new GregorianCalendar(); 952 gCalendar.setTimeInMillis(date); 953 gCalendar.set(java.util.Calendar.HOUR_OF_DAY, 0); 954 gCalendar.set(java.util.Calendar.MINUTE, 0); 955 gCalendar.set(java.util.Calendar.SECOND, 0); 956 gCalendar.set(java.util.Calendar.MILLISECOND, 0); 957 958 Date dateExcluded = gCalendar.getTime(); 959 excludedOccurrences.add(dateExcluded); 960 961 event.setExcludedOccurrences(excludedOccurrences); 962 } 963 else 964 { 965 event.remove(); 966 } 967 968 calendar.saveChanges(); 969 970 // Notify listeners 971 Map<String, Object> eventParams = new HashMap<>(); 972 eventParams.put(ObservationConstants.ARGS_CALENDAR, calendar); 973 eventParams.put(ObservationConstants.ARGS_ID, id); 974 eventParams.put(ObservationConstants.ARGS_NAME, name); 975 eventParams.put(ObservationConstants.ARGS_PATH, path); 976 _observationManager.notify(new Event(ObservationConstants.EVENT_CALENDAR_EVENT_DELETED, _currentUserProvider.getUser(), eventParams)); 977 978 result.put("id", id); 979 result.put("parentId", parentId); 980 981 return result; 982 } 983} 984