All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jfxtras.icalendarfx.itip.ProcessCancel Maven / Gradle / Ivy

There is a newer version: 17-r1
Show newest version
package jfxtras.icalendarfx.itip;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import jfxtras.icalendarfx.VCalendar;
import jfxtras.icalendarfx.components.VComponent;
import jfxtras.icalendarfx.components.VDisplayable;
import jfxtras.icalendarfx.parameters.Range.RangeType;
import jfxtras.icalendarfx.properties.component.relationship.RecurrenceId;
import jfxtras.icalendarfx.properties.component.relationship.UniqueIdentifier;
import jfxtras.icalendarfx.utilities.DateTimeUtilities;

/**
 * 3.2.5.  CANCEL

   The "CANCEL" method in a "VEVENT" calendar component is used to send
   a cancellation notice of an existing event request to the affected
   "Attendees".  The message is sent by the "Organizer" of the event.
   For a recurring event, either the whole event or instances of an
   event may be cancelled.  To cancel the complete range of a recurring
   event, the "UID" property value for the event MUST be specified and a
   "RECURRENCE-ID" MUST NOT be specified in the "CANCEL" method.  In
   order to cancel an individual instance of the event, the
   "RECURRENCE-ID" property value for the event MUST be specified in the
   "CANCEL" method.

   There are two options for canceling a sequence of instances of a
   recurring "VEVENT" calendar component:

   a.  The "RECURRENCE-ID" property for an instance in the sequence MUST
       be specified with the "RANGE" property parameter value of
       "THISANDFUTURE" to indicate cancellation of the specified
       "VEVENT" calendar component and all instances after.

   b.  Individual recurrence instances may be cancelled by specifying
       multiple "VEVENT" components each with a "RECURRENCE-ID" property
       corresponding to one of the instances to be cancelled.

   The "Organizer" MUST send a "CANCEL" message to each "Attendee"
   affected by the cancellation.  This can be done using a single
   "CANCEL" message for all "Attendees" or by using multiple messages
   with different subsets of the affected "Attendees" in each.

   When a "VEVENT" is cancelled, the "SEQUENCE" property value MUST be
   incremented as described in Section 2.1.4.

   This method type is an iCalendar object that conforms to the
   following property constraints:
              +---------------------------------------------+
              | Constraints for a METHOD:CANCEL of a VEVENT |
              +---------------------------------------------+

   +--------------------+----------+-----------------------------------+
   | Component/Property | Presence | Comment                           |
   +--------------------+----------+-----------------------------------+
   | METHOD             | 1        | MUST be CANCEL.                   |
   |                    |          |                                   |
   | VEVENT             | 1+       | All must have the same UID.       |
   |   ATTENDEE         | 0+       | MUST include some or all          |
   |                    |          | Attendees being removed from the  |
   |                    |          | event.  MUST include some or all  |
   |                    |          | Attendees if the entire event is  |
   |                    |          | cancelled.                        |
   |   DTSTAMP          | 1        |                                   |
   |   ORGANIZER        | 1        |                                   |
   |   SEQUENCE         | 1        |                                   |
   |   UID              | 1        | MUST be the UID of the original   |
   |                    |          | REQUEST.                          |
   |   COMMENT          | 0+       |                                   |
   |   ATTACH           | 0+       |                                   |
   |   CATEGORIES       | 0+       |                                   |
   |   CLASS            | 0 or 1   |                                   |
   |   CONTACT          | 0+       |                                   |
   |   CREATED          | 0 or 1   |                                   |
   |   DESCRIPTION      | 0 or 1   |                                   |
   |   DTEND            | 0 or 1   | If present, DURATION MUST NOT be  |
   |                    |          | present.                          |
   |   DTSTART          | 0 or 1   |                                   |
   |   DURATION         | 0 or 1   | If present, DTEND MUST NOT be     |
   |                    |          | present.                          |
   |   EXDATE           | 0+       |                                   |
   |   GEO              | 0 or 1   |                                   |
   |   LAST-MODIFIED    | 0 or 1   |                                   |
   |   LOCATION         | 0 or 1   |                                   |
   |   PRIORITY         | 0 or 1   |                                   |
   |   RDATE            | 0+       |                                   |
   |   RECURRENCE-ID    | 0 or 1   | Only if referring to an instance  |
   |                    |          | of a recurring calendar           |
   |                    |          | component.  Otherwise, it MUST    |
   |                    |          | NOT be present.                   |
   |   RELATED-TO       | 0+       |                                   |
   |   RESOURCES        | 0+       |                                   |
   |   RRULE            | 0 or 1   |                                   |
   |   STATUS           | 0 or 1   | MUST be set to CANCELLED to       |
   |                    |          | cancel the entire event.  If      |
   |                    |          | uninviting specific Attendees,    |
   |                    |          | then MUST NOT be included.        |
   |   SUMMARY          | 0 or 1   |                                   |
   |   TRANSP           | 0 or 1   |                                   |
   |   URL              | 0 or 1   |                                   |
   |   IANA-PROPERTY    | 0+       |                                   |
   |   X-PROPERTY       | 0+       |                                   |
   |   REQUEST-STATUS   | 0        |                                   |
   |                    |          |                                   |
   |   VALARM           | 0        |                                   |
   |                    |          |                                   |
   | VTIMEZONE          | 0+       | MUST be present if any date/time  |
   |                    |          | refers to a timezone.             |
   |                    |          |                                   |
   | IANA-COMPONENT     | 0+       |                                   |
   | X-COMPONENT        | 0+       |                                   |
   |                    |          |                                   |
   | VTODO              | 0        |                                   |
   |                    |          |                                   |
   | VJOURNAL           | 0        |                                   |
   |                    |          |                                   |
   | VFREEBUSY          | 0        |                                   |
   +--------------------+----------+-----------------------------------+
   
* * @author David Bal * */ public class ProcessCancel implements Processable { @Override public List process(VCalendar mainVCalendar, VCalendar iTIPMessage) { List log = new ArrayList<>(); iTIPMessage.childrenUnmodifiable() .stream() .filter(c -> c instanceof VComponent) .map(c -> (VComponent) c) .forEach(c -> { if (c instanceof VDisplayable) { VDisplayable vDisplayable = ((VDisplayable) c); int newSequence = (vDisplayable.getSequence() == null) ? 0 : vDisplayable.getSequence().getValue(); UniqueIdentifier uid = vDisplayable.getUniqueIdentifier(); List> relatedVComponents = mainVCalendar.getVComponents(vDisplayable) .stream() .map(v -> (VDisplayable) v) .filter(v -> v.getUniqueIdentifier().equals(uid)) .collect(Collectors.toList()); RecurrenceId recurrenceID = vDisplayable.getRecurrenceId(); if (! relatedVComponents.isEmpty()) { // match RECURRENCE-ID (if deleting a parent) if (recurrenceID == null) { // delete all related VComponents relatedVComponents.forEach(v -> mainVCalendar.removeChild(v)); log.add("SUCCESS: canceled " + vDisplayable.getClass().getSimpleName() + " with UID:" + vDisplayable.getUniqueIdentifier().getValue()); } else { VDisplayable matchingVComponent = relatedVComponents .stream() .filter(v -> { return Objects.equals(recurrenceID, v.getRecurrenceId()); // Temporal mRecurrenceID = (v.getRecurrenceId() != null) ? v.getRecurrenceId().getValue() : null; // return Objects.equals(recurrenceID, mRecurrenceID); }) .findAny() .orElseGet(() -> null); boolean isMatchFound = matchingVComponent != null; if (isMatchFound) { // delete one recurrence int oldSequence = (matchingVComponent.getSequence() == null) ? 0 : matchingVComponent.getSequence().getValue(); if (newSequence >= oldSequence) { List myVComponents = vDisplayable.calendarList(); myVComponents.remove(matchingVComponent); log.add("SUCCESS: canceled " + c.getClass().getSimpleName() + " with UID:" + vDisplayable.getUniqueIdentifier().getValue()); } else { throw new RuntimeException("Can't cancel because SEQUENCE in cancel message(" + newSequence + ") is lower than sequence of matching component (" + oldSequence + ")."); } // NO MATCH FOUND } else { // add recurrence as EXDATE to parent // find parent repeatable VComponent, if RRULE is absent final VDisplayable parentVComponent; if (vDisplayable.getRecurrenceRule() == null) { parentVComponent = relatedVComponents .stream() .filter(v -> v.getRecurrenceRule() != null) .findAny() .orElseThrow(() -> new RuntimeException("Can't add EXDATE: VComponent with Recurrence Rule can't be found for uid:" + uid)); } else { // vDisplayable.setParent(mainVCalendar); parentVComponent = vDisplayable; } boolean isRecurrence = parentVComponent.isRecurrence(recurrenceID.getValue()); if (isRecurrence) { int oldSequence = (parentVComponent.getSequence() == null) ? 0 : parentVComponent.getSequence().getValue(); if (newSequence >= oldSequence) { // Delete one instance if (recurrenceID.getRange() == null) { if (parentVComponent.getExceptionDates() == null) { parentVComponent.withExceptionDates(recurrenceID.getValue()); } else { parentVComponent.getExceptionDates().get(0).getValue().add(recurrenceID.getValue()); log.add("SUCCESS: canceled " + recurrenceID.getValue() + " for "+ parentVComponent.getClass().getSimpleName() + " with UID:" + vDisplayable.getUniqueIdentifier().getValue()); // parentVComponent.getExceptionDates().add(new ExceptionDates(recurrenceID.getValue())); } // Delete THIS-AND-FUTURE } else if (recurrenceID.getRange().getValue() == RangeType.THIS_AND_FUTURE) { // add UNTIL Temporal previous = parentVComponent.previousStreamValue(recurrenceID.getValue()); final Temporal until; if (previous.isSupported(ChronoUnit.NANOS)) { until = DateTimeUtilities.DateTimeType.DATE_WITH_UTC_TIME.from(previous); } else { until = LocalDate.from(previous); } parentVComponent.getRecurrenceRule().getValue().setUntil(until); log.add("SUCCESS: canceled recurrences after " + until + " for "+ parentVComponent.getClass().getSimpleName() + " with UID:" + vDisplayable.getUniqueIdentifier().getValue()); // Remove orphaned recurrence children List> orphanedChildren = parentVComponent.orphanedRecurrenceChildren(); if (! orphanedChildren.isEmpty()) { mainVCalendar.getVComponents(vDisplayable).removeAll(orphanedChildren); } } else { throw new IllegalArgumentException("Unsupported RangeType:" + recurrenceID.getRange().toString()); } parentVComponent.incrementSequence(); } else { throw new RuntimeException("Can't cancel because SEQUENCE in cancel message(" + newSequence + ") is lower than sequence of matching component (" + oldSequence + ")."); } } } } } } else { // non-displayable VComponents (only VFREEBUSY has UID) // TODO throw new RuntimeException("not implemented"); } }); return log; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy