001/* 002 * Copyright 2014 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.jcr; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.Calendar; 021import java.util.Date; 022import java.util.GregorianCalendar; 023import java.util.List; 024 025import javax.jcr.Node; 026import javax.jcr.NodeIterator; 027import javax.jcr.RepositoryException; 028 029import org.ametys.core.user.UserIdentity; 030import org.ametys.plugins.explorer.calendars.CalendarEvent; 031import org.ametys.plugins.explorer.calendars.CalendarEventAttendee; 032import org.ametys.plugins.explorer.calendars.EventRecurrenceTypeEnum; 033import org.ametys.plugins.explorer.calendars.ModifiableCalendarEvent; 034import org.ametys.plugins.explorer.calendars.helper.RecurrentEventHelper; 035import org.ametys.plugins.repository.AmetysObject; 036import org.ametys.plugins.repository.AmetysRepositoryException; 037import org.ametys.plugins.repository.RepositoryConstants; 038import org.ametys.plugins.repository.jcr.DefaultTraversableAmetysObject; 039import org.ametys.plugins.repository.metadata.CompositeMetadata; 040import org.ametys.plugins.repository.metadata.UnknownMetadataException; 041 042/** 043 * Default implementation of an {@link CalendarEvent}, backed by a JCR node.<br> 044 */ 045public class JCRCalendarEvent extends DefaultTraversableAmetysObject<JCRCalendarEventFactory> implements ModifiableCalendarEvent 046{ 047 /** Metadata's name for author's event* */ 048 public static final String METADATA_CREATOR = "creator"; 049 050 /** Metadata's name for lastModified's event* */ 051 public static final String METADATA_CREATION = "creationDate"; 052 053 /** Metadata's name for last contributor's event* */ 054 public static final String METADATA_CONTRIBUTOR = "contributor"; 055 056 /** Metadata's name for lastModified's event* */ 057 public static final String METADATA_MODIFIED = "lastModified"; 058 059 /** Metadata's name for title's event* */ 060 public static final String METADATA_TITLE = "title"; 061 062 /** Metadata's name for description's event* */ 063 public static final String METADATA_DESC = "description"; 064 065 /** Metadata's name for keywords' event* */ 066 public static final String METADATA_KEYWORDS = "keywords"; 067 068 /** Metadata's name for location's event* */ 069 public static final String METADATA_LOCATION = "location"; 070 071 /** Metadata's name for startDate's event* */ 072 public static final String METADATA_START_DATE = "startDate"; 073 074 /** Metadata's name for endDate's event* */ 075 public static final String METADATA_END_DATE = "endDate"; 076 077 /** Metadata's name for last validated date's event* */ 078 public static final String METADATA_VALIDATED = "lastValidated"; 079 080 /** Metadata's name for last validator's event* */ 081 public static final String METADATA_VALIDATOR = "validator"; 082 083 /** Metadata's name for fullDay's event* */ 084 public static final String METADATA_FULL_DAY = "fullDay"; 085 086 /** Metadata's name for recurrence type event* */ 087 public static final String METADATA_RECURRENCE_TYPE = "recurrenceType"; 088 089 /** Metadata's name for until date event* */ 090 public static final String METADATA_UNTIL_DATE = "untilDate"; 091 092 /** Metadata's name for excluded date event* */ 093 public static final String METADATA_EXCLUDED_DATE = "excludedDate"; 094 095 /** Metadata's name for organiser's event* */ 096 public static final String METADATA_ORGANISER = "organiser"; 097 098 /** Property name for attendee population * */ 099 public static final String PROPERTY_ATTENDEE_POPULATION = "populationId"; 100 101 /** Property name for attendee login * */ 102 public static final String PROPERTY_ATTENDEE_LOGIN = "login"; 103 104 /** Property name for attendee email * */ 105 public static final String PROPERTY_ATTENDEE_EMAIL = "email"; 106 107 /** Property name for attendee external * */ 108 public static final String PROPERTY_ATTENDEE_EXTERNAL = "external"; 109 110 /** Property name for attendee mandatory * */ 111 public static final String PROPERTY_ATTENDEE_MANDATORY = "mandatory"; 112 113 /** Name of the node for attendees * */ 114 public static final String NODE_ATTENDEES_NAME = RepositoryConstants.NAMESPACE_PREFIX + ":calendar-event-attendees"; 115 116 /** Name of the node for attendee * */ 117 public static final String NODE_ATTENDEE_NAME = RepositoryConstants.NAMESPACE_PREFIX + ":calendar-event-attendee"; 118 119 120 /** Property's name for workflow id */ 121 public static final String PROPERTY_WORKFLOW_ID = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":workflowId"; 122 123 /** 124 * Creates an {@link JCRCalendarEvent}. 125 * @param node the node backing this {@link AmetysObject} 126 * @param parentPath the parentPath in the Ametys hierarchy 127 * @param factory the DefaultAmetysObjectFactory which created the AmetysObject 128 */ 129 public JCRCalendarEvent(Node node, String parentPath, JCRCalendarEventFactory factory) 130 { 131 super(node, parentPath, factory); 132 } 133 134 @Override 135 public String getTitle() 136 { 137 return getMetadataHolder().getString(METADATA_TITLE); 138 } 139 140 @Override 141 public String getDescription() 142 { 143 return getMetadataHolder().getString(METADATA_DESC); 144 } 145 146 @Override 147 public String getLocation() 148 { 149 return getMetadataHolder().getString(METADATA_LOCATION, null); 150 } 151 152 @Override 153 public String[] getKeywords() throws AmetysRepositoryException 154 { 155 return getMetadataHolder().getStringArray(METADATA_KEYWORDS, new String[0]); 156 } 157 158 @Override 159 public Date getStartDate() 160 { 161 return getMetadataHolder().getDate(METADATA_START_DATE); 162 } 163 164 @Override 165 public Date getEndDate() 166 { 167 return getMetadataHolder().getDate(METADATA_END_DATE); 168 } 169 170 @Override 171 public Boolean getFullDay() 172 { 173 return getMetadataHolder().getBoolean(METADATA_FULL_DAY); 174 } 175 176 @Override 177 public UserIdentity getCreator() 178 { 179 CompositeMetadata creatorMetadata = getMetadataHolder().getCompositeMetadata(METADATA_CREATOR); 180 return new UserIdentity(creatorMetadata.getString("login"), creatorMetadata.getString("population")); 181 } 182 183 @Override 184 public Date getCreationDate() 185 { 186 return getMetadataHolder().getDate(METADATA_CREATION); 187 } 188 189 @Override 190 public UserIdentity getLastContributor() 191 { 192 CompositeMetadata contributorMetadata = getMetadataHolder().getCompositeMetadata(METADATA_CONTRIBUTOR); 193 return new UserIdentity(contributorMetadata.getString("login"), contributorMetadata.getString("population")); 194 } 195 196 @Override 197 public Date getLastModified() 198 { 199 return getMetadataHolder().getDate(METADATA_MODIFIED); 200 } 201 202 @Override 203 public UserIdentity getLastValidator() throws UnknownMetadataException, AmetysRepositoryException 204 { 205 if (getMetadataHolder().hasMetadata(METADATA_VALIDATOR)) 206 { 207 CompositeMetadata validatorMetadata = getMetadataHolder().getCompositeMetadata(METADATA_VALIDATOR); 208 return new UserIdentity(validatorMetadata.getString("login"), validatorMetadata.getString("population")); 209 } 210 211 return null; 212 } 213 214 @Override 215 public Date getLastValidated() throws UnknownMetadataException, AmetysRepositoryException 216 { 217 if (getMetadataHolder().hasMetadata(METADATA_VALIDATED)) 218 { 219 return getMetadataHolder().getDate(METADATA_VALIDATED); 220 } 221 222 return null; 223 } 224 225 @Override 226 public EventRecurrenceTypeEnum getRecurrenceType() 227 { 228 EventRecurrenceTypeEnum recurrenceEnum = EventRecurrenceTypeEnum.NEVER; 229 if (getMetadataHolder().hasMetadata(METADATA_RECURRENCE_TYPE)) 230 { 231 String recurrenceType = getMetadataHolder().getString(METADATA_RECURRENCE_TYPE); 232 try 233 { 234 recurrenceEnum = EventRecurrenceTypeEnum.valueOf(recurrenceType); 235 } 236 catch (Exception e) 237 { 238 // ignore 239 } 240 } 241 242 return recurrenceEnum; 243 } 244 245 @Override 246 public Boolean isRecurrent() 247 { 248 return !getRecurrenceType().equals(EventRecurrenceTypeEnum.NEVER); 249 } 250 251 @Override 252 public Date getRepeatUntil() 253 { 254 if (getMetadataHolder().hasMetadata(METADATA_UNTIL_DATE)) 255 { 256 return getMetadataHolder().getDate(METADATA_UNTIL_DATE); 257 } 258 259 return null; 260 } 261 262 @Override 263 public List<Date> getExcludedOccurences() 264 { 265 List<Date> excludedOccurences = new ArrayList<>(); 266 if (getMetadataHolder().hasMetadata(METADATA_EXCLUDED_DATE)) 267 { 268 excludedOccurences = Arrays.asList(getMetadataHolder().getDateArray(METADATA_EXCLUDED_DATE)); 269 } 270 return excludedOccurences; 271 } 272 273 @Override 274 public UserIdentity getOrganiser() throws UnknownMetadataException, AmetysRepositoryException 275 { 276 if (getMetadataHolder().hasMetadata(METADATA_ORGANISER)) 277 { 278 CompositeMetadata organiserMetadata = getMetadataHolder().getCompositeMetadata(METADATA_ORGANISER); 279 return new UserIdentity(organiserMetadata.getString("login"), organiserMetadata.getString("population")); 280 } 281 282 return null; 283 } 284 285 @Override 286 public void setTitle(String title) 287 { 288 getMetadataHolder().setMetadata(METADATA_TITLE, title); 289 } 290 291 @Override 292 public void setDescription(String desc) 293 { 294 getMetadataHolder().setMetadata(METADATA_DESC, desc); 295 } 296 297 @Override 298 public void setLocation(String location) 299 { 300 getMetadataHolder().setMetadata(METADATA_LOCATION, location); 301 } 302 303 @Override 304 public void setKeywords(String[] keywords) 305 { 306 getMetadataHolder().setMetadata(METADATA_KEYWORDS, keywords); 307 } 308 309 @Override 310 public void setStartDate(Date startDate) 311 { 312 getMetadataHolder().setMetadata(METADATA_START_DATE, startDate); 313 } 314 315 @Override 316 public void setEndDate(Date endDate) 317 { 318 getMetadataHolder().setMetadata(METADATA_END_DATE, endDate); 319 } 320 321 @Override 322 public void setFullDay(Boolean fullDay) 323 { 324 getMetadataHolder().setMetadata(METADATA_FULL_DAY, fullDay); 325 } 326 327 @Override 328 public void setCreator(UserIdentity user) throws UnknownMetadataException, AmetysRepositoryException 329 { 330 try 331 { 332 Node creatorNode = null; 333 if (getNode().hasNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_CREATOR)) 334 { 335 creatorNode = getNode().getNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_CREATOR); 336 } 337 else 338 { 339 creatorNode = getNode().addNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_CREATOR, RepositoryConstants.USER_NODETYPE); 340 } 341 creatorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login", user.getLogin()); 342 creatorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population", user.getPopulationId()); 343 } 344 catch (RepositoryException e) 345 { 346 throw new AmetysRepositoryException("Error setting the creator property.", e); 347 } 348 } 349 350 @Override 351 public void setCreationDate(Date date) throws UnknownMetadataException, AmetysRepositoryException 352 { 353 getMetadataHolder().setMetadata(METADATA_CREATION, date); 354 } 355 356 @Override 357 public void setLastContributor(UserIdentity user) throws UnknownMetadataException, AmetysRepositoryException 358 { 359 try 360 { 361 Node creatorNode = null; 362 if (getNode().hasNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_CONTRIBUTOR)) 363 { 364 creatorNode = getNode().getNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_CONTRIBUTOR); 365 } 366 else 367 { 368 creatorNode = getNode().addNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_CONTRIBUTOR, RepositoryConstants.USER_NODETYPE); 369 } 370 creatorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login", user.getLogin()); 371 creatorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population", user.getPopulationId()); 372 } 373 catch (RepositoryException e) 374 { 375 throw new AmetysRepositoryException("Error setting the contributor property.", e); 376 } 377 } 378 379 @Override 380 public void setLastModified(Date date) throws UnknownMetadataException, AmetysRepositoryException 381 { 382 getMetadataHolder().setMetadata(METADATA_MODIFIED, date); 383 } 384 385 @Override 386 public void setLastValidator(UserIdentity user) throws UnknownMetadataException, AmetysRepositoryException 387 { 388 try 389 { 390 Node creatorNode = null; 391 if (getNode().hasNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_VALIDATOR)) 392 { 393 creatorNode = getNode().getNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_VALIDATOR); 394 } 395 else 396 { 397 creatorNode = getNode().addNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_VALIDATOR, RepositoryConstants.USER_NODETYPE); 398 } 399 creatorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login", user.getLogin()); 400 creatorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population", user.getPopulationId()); 401 } 402 catch (RepositoryException e) 403 { 404 throw new AmetysRepositoryException("Error setting the validator property.", e); 405 } 406 } 407 408 @Override 409 public void setLastValidated(Date date) throws UnknownMetadataException, AmetysRepositoryException 410 { 411 getMetadataHolder().setMetadata(METADATA_VALIDATED, date); 412 } 413 414 @Override 415 public void setRecurrenceType(String recurrenceType) 416 { 417 getMetadataHolder().setMetadata(METADATA_RECURRENCE_TYPE, recurrenceType); 418 } 419 420 @Override 421 public void setRepeatUntil(Date untilDate) 422 { 423 if (untilDate == null) 424 { 425 if (getMetadataHolder().hasMetadata(METADATA_UNTIL_DATE)) 426 { 427 getMetadataHolder().removeMetadata(METADATA_UNTIL_DATE); 428 } 429 } 430 else 431 { 432 getMetadataHolder().setMetadata(METADATA_UNTIL_DATE, untilDate); 433 } 434 } 435 436 @Override 437 public void setExcludedOccurrences(List<Date> excludedOccurrences) 438 { 439 getMetadataHolder().setMetadata(METADATA_EXCLUDED_DATE, excludedOccurrences.toArray(new Date[excludedOccurrences.size()])); 440 } 441 442 @Override 443 public List<Date> getOccurrences(Date startDate, Date endDate) 444 { 445 Date untilDate = getRepeatUntil(); 446 447 // until date must be included in list of occurrences. 448 // For that purpose, until date is used here as a threshold value that 449 // must be set to the start of the next day 450 if (untilDate != null) 451 { 452 Calendar cal = Calendar.getInstance(); 453 cal.setTime(untilDate); 454 cal.add(Calendar.DATE, 1); 455 cal.set(java.util.Calendar.HOUR_OF_DAY, 0); 456 cal.set(java.util.Calendar.MINUTE, 0); 457 cal.set(java.util.Calendar.SECOND, 0); 458 cal.set(java.util.Calendar.MILLISECOND, 0); 459 untilDate = cal.getTime(); 460 } 461 462 List<Date> occurences = new ArrayList<>(); 463 Date firstDate = getFirstOccurrence(startDate); 464 if (firstDate == null || (untilDate != null && !firstDate.before(untilDate))) 465 { 466 return occurences; 467 } 468 469 List<Date> excludedOccurences = getExcludedOccurences(); 470 471 GregorianCalendar firstDateCalendar = new GregorianCalendar(); 472 firstDateCalendar.setTime(firstDate); 473 firstDateCalendar.set(java.util.Calendar.HOUR_OF_DAY, 0); 474 firstDateCalendar.set(java.util.Calendar.MINUTE, 0); 475 firstDateCalendar.set(java.util.Calendar.SECOND, 0); 476 firstDateCalendar.set(java.util.Calendar.MILLISECOND, 0); 477 478 if (!excludedOccurences.contains(firstDateCalendar.getTime())) 479 { 480 occurences.add(firstDate); 481 } 482 483 Date nextDate = getNextOccurrence(firstDate); 484 while (nextDate != null && nextDate.before(endDate) && (untilDate == null || untilDate.after(nextDate))) 485 { 486 GregorianCalendar nextDateCalendar = new GregorianCalendar(); 487 nextDateCalendar.setTime(nextDate); 488 nextDateCalendar.set(java.util.Calendar.HOUR_OF_DAY, 0); 489 nextDateCalendar.set(java.util.Calendar.MINUTE, 0); 490 nextDateCalendar.set(java.util.Calendar.SECOND, 0); 491 nextDateCalendar.set(java.util.Calendar.MILLISECOND, 0); 492 493 if (!excludedOccurences.contains(nextDateCalendar.getTime())) 494 { 495 occurences.add(nextDate); 496 } 497 nextDate = getNextOccurrence(nextDate); 498 } 499 500 return occurences; 501 } 502 503 @Override 504 public Date getFirstOccurrence(Date date) 505 { 506 Date eventStartDate = getStartDate(); 507 Date eventEndDate = getEndDate(); 508 509 if (eventEndDate.after(date)) 510 { 511 return eventStartDate; 512 } 513 else 514 { 515 long eventDuringTime = eventEndDate.getTime() - eventStartDate.getTime(); 516 517 Date endDate = eventEndDate; 518 Date startDate = eventStartDate; 519 while (startDate != null && endDate.before(date)) 520 { 521 startDate = RecurrentEventHelper.getNextDate(this, startDate); 522 if (startDate != null) 523 { 524 endDate = new Date(startDate.getTime() + eventDuringTime); 525 } 526 } 527 528 return startDate; 529 } 530 } 531 532 @Override 533 public Date getNextOccurrence(Date date) 534 { 535 return RecurrentEventHelper.getNextDate(this, date); 536 } 537 538 @Override 539 public long getWorkflowId() throws AmetysRepositoryException 540 { 541 try 542 { 543 return getNode().getProperty(PROPERTY_WORKFLOW_ID).getLong(); 544 } 545 catch (RepositoryException e) 546 { 547 throw new AmetysRepositoryException("Unable to get workflowId property", e); 548 } 549 } 550 551 @Override 552 public void setWorkflowId(long workflowId) throws AmetysRepositoryException 553 { 554 Node node = getNode(); 555 556 try 557 { 558 node.setProperty(PROPERTY_WORKFLOW_ID, workflowId); 559 } 560 catch (RepositoryException e) 561 { 562 throw new AmetysRepositoryException("Unable to set workflowId property", e); 563 } 564 } 565 566 @Override 567 public long getCurrentStepId() 568 { 569 throw new UnsupportedOperationException(); 570 } 571 572 @Override 573 public void setCurrentStepId(long stepId) 574 { 575 throw new UnsupportedOperationException(); 576 } 577 578 @Override 579 public void setOrganiser(UserIdentity user) 580 { 581 try 582 { 583 Node creatorNode = null; 584 if (getNode().hasNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_ORGANISER)) 585 { 586 creatorNode = getNode().getNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_ORGANISER); 587 } 588 else 589 { 590 creatorNode = getNode().addNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_ORGANISER, RepositoryConstants.USER_NODETYPE); 591 } 592 creatorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login", user.getLogin()); 593 creatorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population", user.getPopulationId()); 594 } 595 catch (RepositoryException e) 596 { 597 throw new AmetysRepositoryException("Error setting the organiser property.", e); 598 } 599 } 600 601 /** 602 * Get attendees to the event 603 * @throws RepositoryException if an error occurred 604 * @return the attendees 605 */ 606 public List<CalendarEventAttendee> getAttendees() throws RepositoryException 607 { 608 List<CalendarEventAttendee> attendees = new ArrayList<>(); 609 610 Node calendarEventNode = getNode(); 611 if (calendarEventNode.hasNode(NODE_ATTENDEES_NAME)) 612 { 613 Node attendeesNode = calendarEventNode.getNode(NODE_ATTENDEES_NAME); 614 NodeIterator nodes = attendeesNode.getNodes(); 615 while (nodes.hasNext()) 616 { 617 Node attendeeNode = (Node) nodes.next(); 618 CalendarEventAttendee attendee = new CalendarEventAttendee(); 619 620 boolean isExternal = attendeeNode.getProperty(PROPERTY_ATTENDEE_EXTERNAL).getBoolean(); 621 if (isExternal) 622 { 623 attendee.setEmail(attendeeNode.getProperty(PROPERTY_ATTENDEE_EMAIL).getString()); 624 } 625 else 626 { 627 attendee.setLogin(attendeeNode.getProperty(PROPERTY_ATTENDEE_LOGIN).getString()); 628 attendee.setPopulationId(attendeeNode.getProperty(PROPERTY_ATTENDEE_POPULATION).getString()); 629 } 630 631 attendee.setIsExternal(isExternal); 632 attendee.setIsMandatory(attendeeNode.getProperty(PROPERTY_ATTENDEE_MANDATORY).getBoolean()); 633 634 attendees.add(attendee); 635 } 636 } 637 638 return attendees; 639 } 640 641 /** 642 * Set attendees to the event 643 * @param attendees the list of attendees 644 * @throws RepositoryException if an error occurred 645 */ 646 public void setAttendees(List<CalendarEventAttendee> attendees) throws RepositoryException 647 { 648 Node calendarEventNode = getNode(); 649 650 if (calendarEventNode.hasNode(NODE_ATTENDEES_NAME)) 651 { 652 calendarEventNode.getNode(NODE_ATTENDEES_NAME).remove(); 653 } 654 655 Node attendeesNode = calendarEventNode.addNode(NODE_ATTENDEES_NAME, "ametys:unstructured"); 656 for (CalendarEventAttendee attendee : attendees) 657 { 658 // Create new attendee 659 Node attendeeNode = attendeesNode.addNode(NODE_ATTENDEE_NAME, "ametys:unstructured"); 660 if (attendee.isExternal()) 661 { 662 attendeeNode.setProperty(PROPERTY_ATTENDEE_EMAIL, attendee.getEmail()); 663 } 664 else 665 { 666 attendeeNode.setProperty(PROPERTY_ATTENDEE_POPULATION, attendee.getPopulationId()); 667 attendeeNode.setProperty(PROPERTY_ATTENDEE_LOGIN, attendee.getLogin()); 668 } 669 670 attendeeNode.setProperty(PROPERTY_ATTENDEE_EXTERNAL, attendee.isExternal()); 671 attendeeNode.setProperty(PROPERTY_ATTENDEE_MANDATORY, attendee.isMandatory()); 672 } 673 } 674 675}