001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2017-2018 microBean.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
014 * implied.  See the License for the specific language governing
015 * permissions and limitations under the License.
016 */
017package org.microbean.kubernetes.controller;
018
019import java.io.Serializable; // for javadoc only
020
021import java.time.Duration;
022import java.time.Instant;
023
024import java.util.EventListener;
025import java.util.EventObject;
026import java.util.Objects;
027
028import io.fabric8.kubernetes.api.model.HasMetadata;
029import io.fabric8.kubernetes.api.model.ObjectMeta;
030
031/**
032 * An {@code abstract} {@link EventObject} that represents another
033 * event that has occurred to a Kubernetes resource, usually as found
034 * in an {@link EventCache} implementation.
035 *
036 * @param <T> a type of Kubernetes resource
037 *
038 * @author <a href="https://about.me/lairdnelson"
039 * target="_parent">Laird Nelson</a>
040 *
041 * @see EventCache
042 */
043public abstract class AbstractEvent<T extends HasMetadata> extends EventObject {
044
045  
046  /*
047   * Static fields.
048   */
049
050
051  /**
052   * The version of this class for {@linkplain Serializable
053   * serialization purposes}.
054   *
055   * @see Serializable
056   */
057  private static final long serialVersionUID = 1L;
058
059
060  /*
061   * Instance fields.
062   */
063  
064
065  /**
066   * The key that identifies this {@link AbstractEvent}'s {@linkplain
067   * #getResource() resource} <strong>only when its final state is
068   * unknown</strong>.
069   *
070   * <p>This field can be&mdash;and often is&mdash;{@code null}.</p>
071   *
072   * @see #getKey()
073   *
074   * @see #setKey(Object)
075   */
076  private volatile Object key;
077
078  /**
079   * The {@link Type} describing the type of this {@link
080   * AbstractEvent}.
081   *
082   * <p>This field is never {@code null}.</p>
083   *
084   * @see #getType()
085   */
086  private final Type type;
087
088  /**
089   * A Kubernetes resource representing the <em>prior</em> state of
090   * the resource returned by this {@link AbstractEvent}'s {@link
091   * #getResource()} method.
092   *
093   * <p>This field may be {@code null}.</p>
094   *
095   * <p>The prior state of a given Kubernetes resource is often not
096   * known, so this field is often {@code null}.</p>
097   *
098   * @see #getResource()
099   */
100  private final T priorResource;
101  
102  /**
103   * A Kubernetes resource representing its state at the time of this
104   * event.
105   *
106   * <p>This field is never {@code null}.</p>
107   *
108   * @see #getResource()
109   */
110  private final T resource;
111
112
113  /*
114   * Constructors.
115   */
116
117
118  /**
119   * A private zero-argument constructor to reinforce to readers and
120   * subclassers alike that this is not only an {@code abstract}
121   * class, but one with a finite, known number of subclasses.
122   *
123   * @exception NullPointerException when invoked
124   *
125   * @see #AbstractEvent(Object, Type, HasMetadata, HasMetadata)
126   */
127  private AbstractEvent() {
128    this(null, null, null, null);
129  }
130  
131  /**
132   * Creates a new {@link AbstractEvent}.
133   *
134   * @param source the creator; must not be {@code null}
135   *
136   * @param type the {@link Type} of this {@link AbstractEvent}; must not be
137   * {@code null}
138   *
139   * @param priorResource a {@link HasMetadata} representing the
140   * <em>prior state</em> of the {@linkplain #getResource() Kubernetes
141   * resource this <code>AbstractEvent</code> primarily concerns}; may
142   * be&mdash;<strong>and often is</strong>&mdash;null
143   *
144   * @param resource a {@link HasMetadata} representing a Kubernetes
145   * resource; must not be {@code null}
146   *
147   * @exception NullPointerException if {@code source}, {@code type}
148   * or {@code resource} is {@code null}
149   *
150   * @exception IllegalStateException if somehow a subclass invoking
151   * this constructor manages illicitly to be neither an instance of
152   * {@link Event} nor an instance of {@link SynchronizationEvent}
153   *
154   * @see Type
155   *
156   * @see EventObject#getSource()
157   */
158  AbstractEvent(final Object source, final Type type, final T priorResource, final T resource) {
159    super(source);
160    if (!(Event.class.isAssignableFrom(this.getClass()) || SynchronizationEvent.class.isAssignableFrom(this.getClass()))) {
161      throw new IllegalStateException("Unexpected subclass");
162    }
163    this.type = Objects.requireNonNull(type);
164    this.priorResource = priorResource;
165    this.resource = Objects.requireNonNull(resource);
166  }
167
168
169  /*
170   * Instance methods.
171   */
172
173
174  /**
175   * Returns a {@link Type} representing the type of this {@link
176   * AbstractEvent}.
177   *
178   * <p>This method never returns {@code null}.</p>
179   *
180   * @return a non-{@code null} {@link Type}
181   *
182   * @see Type
183   */
184  public final Type getType() {
185    return this.type;
186  }
187
188  /**
189   * Returns a {@link HasMetadata} representing the <em>prior
190   * state</em> of the Kubernetes resource this {@link AbstractEvent}
191   * primarily concerns.
192   *
193   * <p>This method may return {@code null}, and often does.</p>
194   *
195   * <p>The prior state of a Kubernetes resource is often not known at
196   * {@link AbstractEvent} construction time so it is common for this method
197   * to return {@code null}.
198   *
199   * @return a {@link HasMetadata} representing the <em>prior
200   * state</em> of the {@linkplain #getResource() Kubernetes resource
201   * this <code>AbstractEvent</code> primarily concerns}, or {@code null}
202   *
203   * @see #getResource()
204   */
205  public final T getPriorResource() {
206    return this.priorResource;
207  }
208
209  /**
210   * Returns a {@link HasMetadata} representing the Kubernetes
211   * resource this {@link AbstractEvent} concerns.
212   *
213   * <p>This method never returns {@code null}.</p>
214   *
215   * @return a non-{@code null} Kubernetes resource
216   */
217  public final T getResource() {
218    return this.resource;
219  }
220
221  /**
222   * Returns {@code true} if this {@link AbstractEvent}'s {@linkplain
223   * #getResource() resource} is an accurate representation of its
224   * last known state.
225   *
226   * <p>This should only return {@code true} for some, but not all,
227   * deletion scenarios.  Any other behavior should be considered to
228   * be an error.</p>
229   *
230   * @return {@code true} if this {@link AbstractEvent}'s {@linkplain
231   * #getResource() resource} is an accurate representation of its
232   * last known state; {@code false} otherwise
233   */
234  public final boolean isFinalStateKnown() {
235    return this.key == null;
236  }
237
238  /**
239   * Sets the key identifying the Kubernetes resource this {@link
240   * AbstractEvent} describes.
241   *
242   * @param key the new key; may be {@code null}
243   *
244   * @see #getKey()
245   */
246  final void setKey(final Object key) {
247    this.key = key;
248  }
249
250  /**
251   * Returns a key that can be used to unambiguously identify this
252   * {@link AbstractEvent}'s {@linkplain #getResource() resource}.
253   *
254   * <p>This method may return {@code null} in exceptional cases, but
255   * normally does not.</p>
256   *
257   * <p>Overrides of this method must not return {@code null} except
258   * in exceptional cases.</p>
259   *
260   * <p>The default implementation of this method returns the return
261   * value of the {@link HasMetadatas#getKey(HasMetadata)} method.</p>
262   *
263   * @return a key for this {@link AbstractEvent}, or {@code null}
264   *
265   * @see HasMetadatas#getKey(HasMetadata) 
266   */
267  public Object getKey() {
268    Object returnValue = this.key;
269    if (returnValue == null) {
270      returnValue = HasMetadatas.getKey(this.getResource());
271    }
272    return returnValue;
273  }
274
275  /**
276   * Returns a hashcode for this {@link AbstractEvent}.
277   *
278   * @return a hashcode for this {@link AbstractEvent}
279   */
280  @Override
281  public int hashCode() {
282    int hashCode = 37;
283    
284    final Object source = this.getSource();
285    int c = source == null ? 0 : source.hashCode();
286    hashCode = hashCode * 17 + c;
287    
288    final Object key = this.getKey();
289    c = key == null ? 0 : key.hashCode();
290    hashCode = hashCode * 17 + c;
291    
292    final Object type = this.getType();
293    c = type == null ? 0 : type.hashCode();
294    hashCode = hashCode * 17 + c;
295    
296    final Object resource = this.getResource();
297    c = resource == null ? 0 : resource.hashCode();
298    hashCode = hashCode * 17 + c;
299
300    final Object priorResource = this.getPriorResource();
301    c = priorResource == null ? 0 : priorResource.hashCode();
302    hashCode = hashCode * 17 + c;
303    
304    return hashCode;
305  }
306
307  /**
308   * Returns {@code true} if the supplied {@link Object} is also an
309   * {@link AbstractEvent} and is equal in every respect to this one.
310   *
311   * @param other the {@link Object} to test; may be {@code null} in
312   * which case {@code false} will be returned
313   *
314   * @return {@code true} if the supplied {@link Object} is also an
315   * {@link AbstractEvent} and is equal in every respect to this one; {@code
316   * false} otherwise
317   */
318  @Override
319  public boolean equals(final Object other) {
320    if (other == this) {
321      return true;
322    } else if (other instanceof AbstractEvent) {
323      
324      final AbstractEvent<?> her = (AbstractEvent<?>)other;
325      
326      final Object source = this.getSource();
327      if (source == null) {
328        if (her.getSource() != null) {
329          return false;
330        }
331      } else if (!source.equals(her.getSource())) {
332        return false;
333      }
334      
335      final Object key = this.getKey();
336      if (key == null) {
337        if (her.getKey() != null) {
338          return false;
339        }
340      } else if (!key.equals(her.getKey())) {
341        return false;
342      }
343      
344      final Object type = this.getType();
345      if (type == null) {
346        if (her.getType() != null) {
347          return false;
348        }
349      } else if (!type.equals(her.getType())) {
350        return false;
351      }
352      
353      final Object resource = this.getResource();
354      if (resource == null) {
355        if (her.getResource() != null) {
356          return false;
357        }
358      } else if (!resource.equals(her.getResource())) {
359        return false;
360      }
361
362      final Object priorResource = this.getPriorResource();
363      if (priorResource == null) {
364        if (her.getPriorResource() != null) {
365          return false;
366        }
367      } else if (!priorResource.equals(her.getPriorResource())) {
368        return false;
369      }
370
371      
372      return true;
373    } else {
374      return false;
375    }
376  }
377
378  /**
379   * Returns a {@link String} representation of this {@link AbstractEvent}.
380   *
381   * <p>This method never returns {@code null}.</p>
382   *
383   * <p>Overrides of this method must not return {@code null}.</p>
384   *
385   * @return a non-{@code null} {@link String} representation of this
386   * {@link AbstractEvent}
387   */
388  @Override
389  public String toString() {
390    final StringBuilder sb = new StringBuilder().append(this.getType()).append(": ");
391    final Object priorResource = this.getPriorResource();
392    if (priorResource != null) {
393      sb.append(priorResource).append(" --> ");
394    }
395    sb.append(this.getResource());
396    return sb.toString();
397  }
398
399
400  /*
401   * Inner and nested classes.
402   */
403
404
405  /**
406   * The type of an {@link AbstractEvent}.
407   *
408   * @author <a href="https://about.me/lairdnelson"
409   * target="_parent">Laird Nelson</a>
410   */
411  public static enum Type {
412
413    /**
414     * A {@link Type} representing the addition of a resource.
415     */
416    ADDITION,
417
418    /**
419     * A {@link Type} representing the modification of a resource.
420     */
421    MODIFICATION,
422
423    /**
424     * A {@link Type} representing the deletion of a resource.
425     */
426    DELETION
427
428  }
429
430}