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