org.silverpeas.core.calendar.CalendarEventOccurrence.java Source code

Java tutorial

Introduction

Here is the source code for org.silverpeas.core.calendar.CalendarEventOccurrence.java

Source

/*
 * Copyright (C) 2000 - 2018 Silverpeas
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * As a special exception to the terms and conditions of version 3.0 of
 * the GPL, you may redistribute this Program in connection with Free/Libre
 * Open Source Software ("FLOSS") applications as described in Silverpeas's
 * FLOSS exception.  You should have received a copy of the text describing
 * the FLOSS exception, and it is also available here:
 * "https://www.silverpeas.org/legal/floss_exception.html"
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.silverpeas.core.calendar;

import org.apache.commons.lang3.tuple.Pair;
import org.silverpeas.core.admin.user.model.User;
import org.silverpeas.core.calendar.CalendarEvent.EventOperationResult;
import org.silverpeas.core.calendar.repository.CalendarEventOccurrenceRepository;
import org.silverpeas.core.contribution.model.Contribution;
import org.silverpeas.core.contribution.model.ContributionIdentifier;
import org.silverpeas.core.contribution.model.WysiwygContent;
import org.silverpeas.core.date.Period;
import org.silverpeas.core.persistence.Transaction;
import org.silverpeas.core.persistence.datasource.model.IdentifiableEntity;
import org.silverpeas.core.persistence.datasource.model.identifier.ExternalStringIdentifier;
import org.silverpeas.core.persistence.datasource.model.jpa.BasicJpaEntity;
import org.silverpeas.core.reminder.WithReminder;
import org.silverpeas.core.util.Mutable;

import javax.persistence.*;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.temporal.Temporal;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Optional;

import static org.silverpeas.core.persistence.datasource.model.jpa.JpaEntityReflection.setCreationData;
import static org.silverpeas.core.persistence.datasource.model.jpa.JpaEntityReflection.setUpdateData;

/**
 * The occurrence of an event in a Silverpeas calendar. It is an instance of an event in the
 * timeline of a calendar; it represents an event starting and ending at a given date or datetime
 * in the calendar.
 *
 * A non-recurrent event is a singleton, meaning that is has only one single instance occurring in
 * the calendar (so the name occurrence). A recurrent event has one or more occurrences in the
 * timeline. It occurs several time in the calendar in a regular way according to its recurrence
 * rule; at each time such an event occurs is represented by an occurrence.
 *
 * By default, the occurrences of an event aren't persisted but they are generated from the period
 * of time at which occurs the event and, if any, from its recurrence rule. If an occurrence of a
 * non-recurrent event is deleted, then the related event is deleted. If an occurrence of a
 * recurrent event is deleted, then an exception is added into the recurrence rule of the event.
 * This operation is done with one of the following methods:
 * {@link #delete()},
 * {@link #deleteSinceMe()}.
 * If an occurrence of a non-recurrent event is modified, then the modification is directly
 * applied to the event itself (as it is a singleton). If an occurrence of a recurrent event is
 * modified, then the modification is applied to the occurrence only and this occurrence is
 * persisted as a modification related to the recurrence rule of the concerned event.
 */
@Entity
@Table(name = "sb_cal_occurrences")
@NamedQueries({
        @NamedQuery(name = "occurrenceByEventsAndByPeriod", query = "SELECT o FROM CalendarEventOccurrence o WHERE o.event in :events AND "
                + "((o.component.period.startDateTime <= :startDateTime AND "
                + "  o.component.period.endDateTime > :startDateTime) OR "
                + "(o.component.period.startDateTime >= :startDateTime AND "
                + "  o.component.period.startDateTime < :endDateTime))"),
        @NamedQuery(name = "occurrenceByEventSince", query = "SELECT o FROM CalendarEventOccurrence o WHERE o.event = :event AND "
                + "o.component.period.startDateTime >= :date"),
        @NamedQuery(name = "occurrenceByEvent", query = "SELECT o FROM CalendarEventOccurrence o WHERE o.event "
                + "= :event") })
public class CalendarEventOccurrence extends BasicJpaEntity<CalendarEventOccurrence, ExternalStringIdentifier>
        implements IdentifiableEntity, Occurrence, Contribution, WithReminder {

    public static final String TYPE = "CalendarEventOccurrence";

    public static final Comparator<CalendarEventOccurrence> COMPARATOR_BY_ORIGINAL_DATE_ASC = Comparator
            .comparing(o -> o.getOriginalStartDate().toString());
    public static final Comparator<CalendarEventOccurrence> COMPARATOR_BY_DATE_ASC = Comparator
            .comparing(o -> o.getStartDate().toString());
    public static final Comparator<CalendarEventOccurrence> COMPARATOR_BY_DATE_DESC = (o1, o2) -> o2.getStartDate()
            .toString().compareTo(o1.getStartDate().toString());

    @ManyToOne(optional = false, fetch = FetchType.EAGER)
    @JoinColumn(name = "eventId", referencedColumnName = "id")
    private CalendarEvent event;

    @OneToOne(optional = false, fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "componentId", referencedColumnName = "id", unique = true)
    private CalendarComponent component;
    @Transient
    private CalendarEventOccurrence previousState;

    /**
     * Constructor for only persistence context.
     */
    protected CalendarEventOccurrence() {
        // this constructor is dedicated to be used by the persistence context.
    }

    /**
     * Constructs a new occurrence from the specified calendar event, starting and ending at the
     * specified dates.
     * @param event the event from which the occurrence is instantiated.
     * @param startDate the start date (and time if offset) of the occurrence.
     * @param endDate the end date (and time if offset) of the occurrence.
     */
    CalendarEventOccurrence(final CalendarEvent event, final Temporal startDate, final Temporal endDate) {
        setId(generateId(event, startDate));
        this.event = event;
        this.component = event.asCalendarComponent().clone();
        setCreationData(this.component, event.getCreator(), event.getCreationDate());
        setUpdateData(this.component, event.getLastUpdater(), event.getLastUpdateDate());
        this.component.setPeriod(Period.between(startDate, endDate));
    }

    /**
     * Gets optionally an event occurrence from the specified data.
     * @param event an event.
     * @param occurrenceStartDate a start date.
     * @return the computed occurrence identifier.
     */
    public static Optional<CalendarEventOccurrence> getBy(CalendarEvent event, String occurrenceStartDate) {
        return getBy(event, getDate(occurrenceStartDate));
    }

    /**
     * Gets optionally an event occurrence from the specified data.
     * @param event an event.
     * @param occurrenceStartDate a start date.
     * @return the computed occurrence identifier.
     */
    public static Optional<CalendarEventOccurrence> getBy(CalendarEvent event, Temporal occurrenceStartDate) {
        Temporal startDate = occurrenceStartDate;
        if (startDate instanceof OffsetDateTime) {
            startDate = ((OffsetDateTime) occurrenceStartDate).atZoneSameInstant(ZoneOffset.UTC).toOffsetDateTime();
        }
        return getById(generateId(event, startDate));
    }

    /**
     * Gets optionally an event occurrence by its identifier.
     * <p>If the occurrence exists into the persistence, it is returned. Otherwise it is generated.
     * <p>Otherwise and if start date is valid, the occurrence is generated.
     * @param id the identifier of the aimed occurrence.
     * @return an optional calendar event occurrence.
     */
    public static Optional<CalendarEventOccurrence> getById(final String id) {
        final CalendarEventOccurrenceRepository repository = CalendarEventOccurrenceRepository.get();
        final Mutable<CalendarEventOccurrence> occurrence = Mutable.ofNullable(repository.getById(id));
        if (!occurrence.isPresent()) {
            final Pair<String, Temporal> explodedId = explodeId(id);
            final String eventId = explodedId.getLeft();
            final Temporal startDate = explodedId.getRight();
            final Optional<CalendarEvent> event = Optional.ofNullable(CalendarEvent.getById(eventId));
            event.ifPresent(e -> {
                if (e.isRecurrent()) {
                    final LocalDate occStartDate;
                    final LocalDate occEndDate;
                    if (startDate instanceof LocalDate) {
                        final LocalDate date = (LocalDate) startDate;
                        occStartDate = date.minusDays(1);
                        occEndDate = date.plusDays(1);
                    } else {
                        final OffsetDateTime dateTime = (OffsetDateTime) startDate;
                        occStartDate = dateTime.minusDays(1).toLocalDate();
                        occEndDate = dateTime.plusDays(1).toLocalDate();
                    }
                    final List<CalendarEventOccurrence> occurrences = e.getCalendar()
                            .between(occStartDate, occEndDate).getEventOccurrences();
                    occurrences.removeIf(o -> !o.getCalendarEvent().getId().equals(eventId)
                            || (!o.getStartDate().equals(startDate)));
                    if (occurrences.size() == 1) {
                        occurrence.set(occurrences.get(0));
                    }
                } else {
                    occurrence.set(new CalendarEventOccurrence(e, e.getStartDate(), e.getEndDate()));
                }
            });
        }
        return Optional.ofNullable(occurrence.orElse(null));
    }

    private static CalendarEventOccurrenceGenerator generator() {
        return CalendarEventOccurrenceGenerator.get();
    }

    /**
     * Gets the event occurrences that occur in the specified window of time of a given calendar.
     * @param timeWindow a window of time of a calendar.
     * @return a list of event occurrences or an empty list if there is no occurrences of events
     * in the specified window of time.
     */
    static List<CalendarEventOccurrence> getOccurrencesIn(final CalendarTimeWindow timeWindow) {
        List<CalendarEventOccurrence> occurrences = generator().generateOccurrencesIn(timeWindow);
        List<CalendarEventOccurrence> modified = CalendarEventOccurrenceRepository.get()
                .getAll(timeWindow.getEvents(), timeWindow.getPeriod());
        modified.forEach(o -> {
            int idx = occurrences.indexOf(o);
            if (idx < 0) {
                occurrences.add(o);
            } else {
                occurrences.set(idx, o);
            }
        });
        return occurrences;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Optional<CalendarEvent> getParent() {
        return Optional.ofNullable(getCalendarEvent());
    }

    @Override
    public ContributionIdentifier getContributionId() {
        return ContributionIdentifier.from(getCalendarEvent().getCalendar().getComponentInstanceId(), getId(),
                getContributionType());
    }

    @Override
    public User getCreator() {
        return component.getCreator();
    }

    @Override
    public Date getCreationDate() {
        return component.getCreationDate();
    }

    @Override
    public User getLastModifier() {
        return component.getLastUpdater();
    }

    @Override
    public Date getLastModificationDate() {
        return component.getLastUpdateDate();
    }

    @Override
    public boolean canBeAccessedBy(final User user) {
        return getCalendarEvent().canBeAccessedBy(user);
    }

    @Override
    public String getContributionType() {
        return TYPE;
    }

    /**
     * Gets the event from which this occurrence was spawned.
     *
     * From the returned event, the title, the description or any other event properties can be
     * modified. Nevertheless, the change can be effective only by invoking the {@code update} method
     * of either the {@link CalendarEvent} to apply the modifications to all occurrences or this
     * occurrence to apply the modifications only to this occurrence. Only the period at which the
     * event occur in the calendar cannot be used to update this occurrence. For doing, please use
     * either the {@code setPeriod} or the {@code setDay} method of {@link CalendarEventOccurrence}.
     * @return the event from which this occurrence is instanciated.
     */
    public CalendarEvent getCalendarEvent() {
        return this.event;
    }

    @Override
    public Temporal getStartDate() {
        return this.component.getPeriod().getStartDate();
    }

    @Override
    public Temporal getEndDate() {
        return this.component.getPeriod().getEndDate();
    }

    /**
     * Gets the original start date of this occurrence. If the start date wasn't modified, then the
     * returning date should be the same than the start date returned by the method
     * {@link CalendarEventOccurrence#getStartDate()}.
     * @return the original start date of this occurrence of calendar event.
     */
    public Temporal getOriginalStartDate() {
        return explodeId(this.getId()).getRight();
    }

    /**
     * Changes the planning of this occurrence in the calendar. The change will be effectively
     * performed once the {@code update} method invoked.
     * @param newPeriod a new period of time on which this occurrence will occur or has actually
     * occurred.
     */
    public void setPeriod(final Period newPeriod) {
        this.component.setPeriod(newPeriod);
    }

    /**
     * Gets the period of this occurrence in the calendar.
     * @return a period.
     */
    public Period getPeriod() {
        return this.component.getPeriod();
    }

    /**
     * Changes the planning of this occurrence in the calendar. The change will be effectively
     * performed once the {@code update} method invoked.
     * @param newDay the new day at which this occurrence will occur or has actually occurred.
     */
    public void setDay(final LocalDate newDay) {
        Period newPeriod = Period.between(newDay, newDay);
        setPeriod(newPeriod);
    }

    /**
     * Gets the title of this event occurrence. The title is either the one of the related event or
     * the one that was modified for this occurrence.
     * @return the title of the event occurrence.
     */
    @Override
    public String getTitle() {
        return this.component.getTitle();
    }

    public void setTitle(final String title) {
        this.component.setTitle(title);
    }

    public AttendeeSet getAttendees() {
        return this.component.getAttendees();
    }

    /**
     * Gets the description of this event occurrence. The description is either the one of the
     * related event or the one that was modified for this occurrence.
     * @return the description of the event occurrence.
     */
    @Override
    public String getDescription() {
        return this.component.getDescription();
    }

    /**
     * Sets a new description for this event occurrence.
     * @param description a new description related to this event occurrence.
     */
    public void setDescription(String description) {
        this.component.setDescription(description);
    }

    /**
     * Gets the location of this event occurrence. The location is either the one of the related
     * event or the one that was modified for this occurrence.
     * @return the location of the event occurrence.
     */
    public String getLocation() {
        return this.component.getLocation();
    }

    /**
     * Sets a new location for this event occurrence.
     * @param location the new location where this occurrence should take place.
     */
    public void setLocation(String location) {
        this.component.setLocation(location);
    }

    /**
     * Gets the attributes of this event occurrence. The attributes are either those related
     * to the event or those that were modified for this occurrence.
     * @return the extra attributes of the event occurrence.
     */
    public AttributeSet getAttributes() {
        return this.component.getAttributes();
    }

    /**
     * Gets the priority of this event occurrence. The priority is the one that is set for the event.
     * @return the priority of the event occurrence.
     */
    public Priority getPriority() {
        return this.component.getPriority();
    }

    /**
     * Sets a new priority to this event occurrence.
     * @param priority the new priority of this event occurrence.
     */
    public void setPriority(final Priority priority) {
        this.component.setPriority(priority);
    }

    /**
     * Gets the categories of this event occurrence. The categories are those that are set for the
     * event.
     * @return the categories of the event occurrence.
     */
    public CategorySet getCategories() {
        return this.event.getCategories();
    }

    /**
     * Gets the level of visibility of this event occurrence. The visibility level is the one that is
     * set for the event.
     * @return the level of visibility of the event occurrence.
     */
    public VisibilityLevel getVisibilityLevel() {
        return this.event.getVisibilityLevel();
    }

    /**
     * Is this event occurrence occurring on all the day(s)?
     * @return true if this event occurrence is occurring on all its day(s).
     */
    public boolean isOnAllDay() {
        return getPeriod().isInDays();
    }

    /**
     * Gets the revision sequence number of this event occurrence within a sequence of revisions.
     * Any changes to some properties of this event occurrence increment this sequence number. This
     * number is mainly dedicated with the synchronization or syndication mechanism of event instances
     * with external calendars. Its meaning comes from the icalendar specification.
     * @return the sequence number of this event occurrence.
     * @see CalendarComponent#getSequence()
     */
    public long getSequence() {
        return this.component.isPersisted() ? this.component.getSequence() : this.event.getSequence();
    }

    /**
     * Gets the last date at which this occurrence has been updated.
     * @return the last update date.
     */
    public Date getLastUpdateDate() {
        return this.component.getLastUpdateDate();
    }

    /**
     * Gets the content of this event occurrence. The content is the one of the event and it cannot
     * be modified per occurrence but for the whole event occurrence(s).
     * @return optionally the content of the event.
     */
    public Optional<WysiwygContent> getContent() {
        return getCalendarEvent().getContent();
    }

    /**
     * Is this occurrence actually occurs before the specified occurrence?
     * @param occurrence another occurrence with which the start date is compared.
     * @return true if this occurrence starts before the specified another one. False otherwise.
     */
    public boolean isBefore(final CalendarEventOccurrence occurrence) {
        return getStartDate().toString().compareTo(occurrence.getStartDate().toString()) < 0;
    }

    /**
     * Gets the {@link CalendarComponent} representation of this occurrence. Any change to the
     * returned calendar component will change also the related occurrence.
     * @return a {@link CalendarComponent} instance representing this event occurrence (without the
     * specific properties related to an event occurrence).
     */
    public CalendarComponent asCalendarComponent() {
        return this.component;
    }

    /**
     * Updates this occurrence and all of the forthcoming occurrences of the same event with the
     * changes in this occurrence.
     *
     * If the event is non recurrent, then the event is itself updated. Otherwise a new event is
     * created for this occurrence and all of the forthcoming occurrences and with the modifications
     * carried by this occurrences. The recurrence of the original event is updated to end up at this
     * occurrence minus one day (the recurrence end date is inclusive).
     *
     * In the case the temporal period of the occurrences is modified, the participation status of
     * all the attendees in the occurrences is cleared.
     *
     * This is equivalent to
     * <pre>{@code EventOperationResult result = event.updateSince(this)}</pre>
     *
     * @see CalendarEvent#updateSince(CalendarEventOccurrence)
     *
     * @return the result of the update.
     */
    public EventOperationResult updateSinceMe() {
        return getCalendarEvent().updateSince(this);
    }

    /**
     * Updates only this occurrence among the occurrences of the event it comes from.
     *
     * If the event is non recurrent, then the event is itself updated. Otherwise the changes in
     * this occurrence are persisted and its sequence number is incremented by one, diverging then
     * from the sequence number of the event it comes from.
     * <p>
     * In the case the temporal period of the occurrence is modified, the participation status
     * of all the attendees in this occurrence is cleared.
     *
     *  This is equivalent to
     * <pre>{@code EventOperationResult result = event.updateOnly(this)}</pre>
     *
     * @see CalendarEvent#updateOnly(CalendarEventOccurrence)
     *
     * @return the result of the update.
     */
    public EventOperationResult update() {
        return getCalendarEvent().updateOnly(this);
    }

    /**
     * Updates this occurrence with the state of the specified occurrence.
     * @param occurrence an event occurrence from which this occurrence will be updated.
     * @return the result of the update.
     */
    public EventOperationResult updateFrom(final CalendarEventOccurrence occurrence) {
        occurrence.asCalendarComponent().copyTo(this.component);
        return update();
    }

    /**
     * Deletes this occurrence and all of the forthcoming occurrences of the same event.
     *
     * If the event is non recurrent, then the event is itself deleted. Otherwise the
     * original starting date of this occurrence and of all of the forthcoming occurrences are added
     * in the exception dates of the event's recurrence rule. If some of the occurrences were
     * persisted, then they are all removed from the persistence context.
     *
     *  This is equivalent to
     * <pre>{@code EventOperationResult result = event.deleteSince(this)}</pre>
     *
     * @see CalendarEvent#deleteSince(CalendarEventOccurrence)
     *
     * @return the result of the deletion.
     */
    public EventOperationResult deleteSinceMe() {
        return getCalendarEvent().deleteSince(this);
    }

    /**
     * Deletes only this occurrence among the occurrences of the event it comes from.
     *
     * If the event is non recurrent, then the event is itself deleted. Otherwise the
     * original starting date of this occurrence is added in the exception dates of the event's
     * recurrence rule. If the occurrence was previously persisted, then it is removed from the
     * persistence context.
     *
     * This is equivalent to
     * <pre>{@code EventOperationResult result = event.deleteOnly(this)}</pre>
     *
     * @see CalendarEvent#deleteOnly(CalendarEventOccurrence)
     *
     * @return the result of the deletion.
     */
    public EventOperationResult delete() {
        return getCalendarEvent().deleteOnly(this);
    }

    CalendarEventOccurrence cloneWithEvent(final CalendarEvent event) {
        CalendarEventOccurrence newOccurrence = new CalendarEventOccurrence();
        newOccurrence.setId(generateId(event, getOriginalStartDate()));
        newOccurrence.event = event;
        newOccurrence.component = this.component.clone();
        newOccurrence.component.setCalendar(event.getCalendar());
        return newOccurrence;
    }

    /**
     * Is the properties of this event occurrence was modified since its last specified state?
     * The attendees in this event occurrence aren't taken into account as they aren't considered as
     * a property of an event occurrence.
     * @param previous a previous state of this event occurrence.
     * @return true if the state of this event occurrence is different with the specified one.
     */
    public boolean isModifiedSince(final CalendarEventOccurrence previous) {
        if (!this.getId().equals(previous.getId())) {
            throw new IllegalArgumentException(
                    "The event occurrence of id " + previous.getId() + " isn't the expected one " + this.getId());
        }
        if (this.getVisibilityLevel() != previous.getVisibilityLevel()
                || !this.getCategories().equals(previous.getCategories())) {
            return true;
        }

        return this.asCalendarComponent().isModifiedSince(previous.asCalendarComponent());
    }

    /**
     * Converts this occurrence of a calendar event into an unplanned non-recurrent
     * {@link CalendarEvent} instance. This method is dedicated to the {@link CalendarEvent} class.
     * @return a new {@link CalendarEvent} instance from this occurrence.
     * @see CalendarEvent#from(CalendarEventOccurrence)
     */
    CalendarEvent toCalendarEvent() {
        return CalendarEvent.from(this);
    }

    /**
     * Converts this occurrence of a calendar event into an unplanned recurrent {@link CalendarEvent}
     * instance. This method is dedicated to the {@link CalendarEvent} class.
     * <p>
     * If the occurrence comes from a non-recurrent event, then the returned event won't be
     * neither recurrent. Otherwise the recurrence of the new event will start at the start date of
     * this occurrence and will end up at the actual end date of the recurrence of the event of this
     * occurrence.
     * @return a new possibly recurrent {@link CalendarEvent} instance from this occurrence.
     * @see CalendarEvent#from(CalendarEventOccurrence)
     */
    CalendarEvent toRecurrentCalendarEvent() {
        CalendarEvent newEvent = toCalendarEvent();
        if (this.getCalendarEvent().isRecurrent()) {
            Recurrence recurrence = this.getCalendarEvent().getRecurrence().clone();
            recurrence.clearsAllExceptionDates();
            if (!this.getCalendarEvent().getRecurrence().isEndless()) {
                recurrence.until(this.getCalendarEvent().getRecurrence().getEndDate()
                        .orElseThrow(IllegalArgumentException::new));
            }
            newEvent.recur(recurrence);
        }
        return newEvent;
    }

    /**
     * Sets the event from which this occurrence comes from. This method is used for internal
     * mechanisms of the Silverpeas Calendar Engine when working with copies of calendar event
     * occurrences.
     * @param event the event of this occurrence.
     */
    final void setCalendarEvent(final CalendarEvent event) {
        setId(generateId(event, getOriginalStartDate()));
        this.event = event;
        this.component.setCalendar(event.getCalendar());
    }

    /**
     * Saves this occurrence of a calendar event into a data source so that it can be get later.
     * This method is dedicated to the {@link CalendarEvent} class.
     * <p>
     * Saving an event occurrence is done when this occurrence has changed from the event or from
     * its original planning in the timeline of a calendar. This is only done with occurrences of
     * a recurrent event as any change to the single occurrence of a non-recurrent event modifies the
     * event itself.
     * @return the persisted event occurrence.
     */
    CalendarEventOccurrence saveIntoPersistence() {
        return Transaction.performInOne(() -> {
            CalendarEventOccurrenceRepository repository = CalendarEventOccurrenceRepository.get();
            final CalendarEventOccurrence previous = getPreviousState();
            final CalendarComponent pcc;
            final boolean modifiedSince;
            if (previous != null) {
                pcc = previous.asCalendarComponent();
                modifiedSince = isModifiedSince(previous);
            } else {
                pcc = event.asCalendarComponent();
                // Making a clone of event component and setting to it the component period in order to
                // not detect a modification about the period when performing method #isModifiedSince().
                // The clone permits to not imply a modification of event component into persistence. As
                // the below treatment is performed into the case of creation, the original start date is
                // compared to the current start date in order to try to detect a real period modification.
                final CalendarComponent pccClone = pcc.clone();
                pccClone.setPeriod(component.getPeriod());
                modifiedSince = component.isModifiedSince(pccClone)
                        || !getOriginalStartDate().toString().equals(getStartDate().toString());
            }
            if (!modifiedSince && getAttendees().onlyAttendeePropertyChange(pcc.getAttendees())) {
                // we don't want update properties to be modified on participation answer or presence
                // status change in the attendees
                this.component.createdBy(pcc.getCreator(), pcc.getCreationDate()).updatedBy(pcc.getLastUpdater(),
                        pcc.getLastUpdateDate());
            } else {
                this.component.incrementSequence();
            }
            return repository.save(this);
        });
    }

    /**
     * Deletes this occurrence of a calendar event in the data source. If this occurrences wasn't
     * persisted, then nothing is done.
     * This method is dedicated to the {@link CalendarEvent} class.
     */
    void deleteFromPersistence() {
        Transaction.performInOne(() -> {
            CalendarEventOccurrenceRepository repository = CalendarEventOccurrenceRepository.get();
            repository.delete(this);
            return null;
        });
    }

    /**
     * Deletes this occurrence and all the occurrences belonging to the same event of this occurrence
     * and that are after this one. This method is dedicated to the {@link CalendarEvent} class.
     * @return the count of actually occurrences removed from the data source.
     */
    long deleteAllSinceMeFromThePersistence() {
        return Transaction.performInOne(() -> {
            CalendarEventOccurrenceRepository repository = CalendarEventOccurrenceRepository.get();
            List<CalendarEventOccurrence> occurrences = repository.getAllSince(this);
            repository.delete(occurrences);
            return occurrences.size();
        });
    }

    /**
     * Is the date on which this occurrence is planned has changed from its previous state.
     * @return true if the date of this occurrence has just modified. False otherwise.
     */
    boolean isDateChanged() {
        CalendarEventOccurrence previous = getPreviousState();
        return (previous != null && !previous.getPeriod().equals(this.getPeriod()))
                || (previous == null && !this.getOriginalStartDate().equals(this.getStartDate()));
    }

    private CalendarEventOccurrence getPreviousState() {
        if (previousState == null && this.getId() != null) {
            previousState = Transaction.performInNew(() -> {
                CalendarEventOccurrenceRepository repository = CalendarEventOccurrenceRepository.get();
                return repository.getById(this.getId());
            });
        }
        return previousState;
    }

    /**
     * Generates an event occurrence identifier from the necessary data.
     * @param event an event.
     * @param occurrenceStartDate a start date.
     * @return the computed occurrence identifier.
     */
    private static String generateId(CalendarEvent event, Temporal occurrenceStartDate) {
        return event.getId() + "@" + occurrenceStartDate;
    }

    private static Pair<String, Temporal> explodeId(String id) {
        final String[] explodedId = id.split("@");
        return Pair.of(explodedId[0], getDate(explodedId[1]));
    }

    private static Temporal getDate(String temporal) {
        if (temporal.contains("T")) {
            return OffsetDateTime.parse(temporal);
        } else {
            return LocalDate.parse(temporal);
        }
    }
}