AbstractDomainEvent.java

package com.reallifedeveloper.common.domain.event;

import java.time.ZonedDateTime;
import java.util.Objects;

import org.checkerframework.checker.nullness.qual.Nullable;

import com.reallifedeveloper.common.domain.ErrorHandling;
import com.reallifedeveloper.common.domain.registry.CommonDomainRegistry;

/**
 * An abstract base class for domain events.
 *
 * @author RealLifeDeveloper
 */
public abstract class AbstractDomainEvent implements DomainEvent {

    private static final long serialVersionUID = 1L;

    /**
     * The timestamp when this event occurred.
     */
    private final ZonedDateTime eventOccurredOn;

    /**
     * The version of this event. In a long-lived system, it may be necessary to work with old versions of domaim events and keeping track
     * of the version makes this easier.
     */
    private final int eventVersion;

    /**
     * Creates a new {@code AbstractDomainEvent} that occurred now and has a version of 1.
     * <p>
     * The time of occurrence is taken from calling the {@link com.reallifedeveloper.common.domain.TimeService#now()} method on the
     * {@link CommonDomainRegistry#timeService()}.
     */
    public AbstractDomainEvent() {
        this(CommonDomainRegistry.timeService().now(), 1);
    }

    /**
     * Creates a new {@code AbstractDomainEvent} that occurred now and has the given version.
     * <p>
     * The time of occurrence is taken from calling the {@link com.reallifedeveloper.common.domain.TimeService#now()} method on the
     * {@link CommonDomainRegistry#timeService()}.
     *
     * @param eventVersion the version of the event
     */
    public AbstractDomainEvent(int eventVersion) {
        this(CommonDomainRegistry.timeService().now(), eventVersion);
    }

    /**
     * Creates a new {@code AbstractDomainEvent} that occurred at the given time and has a version of 1.
     *
     * @param eventOccurredOn the date and time the event occurred
     */
    public AbstractDomainEvent(ZonedDateTime eventOccurredOn) {
        this(eventOccurredOn, 1);
    }

    /**
     * Creates a new {@code AbstractDomainEvent} that occurred at the given time and has the given version.
     *
     * @param eventOccurredOn the time the event occurred
     * @param eventVersion    the version of the event
     */
    public AbstractDomainEvent(ZonedDateTime eventOccurredOn, int eventVersion) {
        ErrorHandling.checkNull("eventOccurredOn must not be null", eventOccurredOn);
        this.eventOccurredOn = eventOccurredOn;
        this.eventVersion = eventVersion;
    }

    @Override
    public ZonedDateTime eventOccurredOn() {
        return eventOccurredOn;
    }

    @Override
    public int eventVersion() {
        return eventVersion;
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + "{eventOccurredOn=" + eventOccurredOn() + ", eventVersion=" + eventVersion() + "}";
    }

    @Override
    public int hashCode() {
        return Objects.hash(eventOccurredOn, eventVersion);
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        AbstractDomainEvent other = (AbstractDomainEvent) obj;
        return Objects.equals(eventOccurredOn, other.eventOccurredOn) && Objects.equals(eventVersion, other.eventVersion);
    }

    /**
     * Make finalize method final to avoid "Finalizer attacks" and corresponding SpotBugs warning (CT_CONSTRUCTOR_THROW).
     *
     * @see <a href="https://wiki.sei.cmu.edu/confluence/display/java/OBJ11-J.+Be+wary+of+letting+constructors+throw+exceptions">
     *      Explanation of finalizer attack</a>
     */
    @Override
    @SuppressWarnings({ "checkstyle:NoFinalizer", "PMD.EmptyFinalizer", "PMD.EmptyMethodInAbstractClassShouldBeAbstract" })
    protected final void finalize() throws Throwable {
        // Do nothing
    }
}