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