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.beans.PropertyChangeEvent;
020import java.beans.PropertyChangeListener;
021
022import java.io.Closeable;
023import java.io.IOException;
024
025import java.time.Duration;
026
027import java.util.Map;
028import java.util.Objects;
029
030import java.util.concurrent.Future;
031import java.util.concurrent.ScheduledExecutorService;
032
033import java.util.function.Consumer;
034
035import java.util.logging.Level;
036import java.util.logging.Logger;
037
038import io.fabric8.kubernetes.api.model.HasMetadata;
039import io.fabric8.kubernetes.api.model.KubernetesResourceList;
040
041import io.fabric8.kubernetes.client.Watcher;
042
043import io.fabric8.kubernetes.client.dsl.Listable;
044import io.fabric8.kubernetes.client.dsl.VersionWatchable;
045
046import net.jcip.annotations.Immutable;
047import net.jcip.annotations.ThreadSafe;
048
049import org.microbean.development.annotation.NonBlocking;
050
051/**
052 * A convenient combination of a {@link Reflector}, a {@link
053 * VersionWatchable} and {@link Listable} implementation, an
054 * (internal) {@link EventQueueCollection}, a {@link Map} of known
055 * Kubernetes resources and an {@link EventQueue} {@link Consumer}
056 * that {@linkplain Reflector#start() mirrors Kubernetes cluster
057 * events} into a {@linkplain EventQueueCollection collection of
058 * <code>EventQueue</code>s} and {@linkplain
059 * EventQueueCollection#start(Consumer) arranges for their consumption
060 * and processing}.
061 *
062 * <p>{@linkplain #start() Starting} a {@link Controller} {@linkplain
063 * EventQueueCollection#start(Consumer) starts the
064 * <code>Consumer</code>} supplied at construction time, and
065 * {@linkplain Reflector#start() starts the embedded
066 * <code>Reflector</code>}.  {@linkplain #close() Closing} a {@link
067 * Controller} {@linkplain Reflector#close() closes its embedded
068 * <code>Reflector</code>} and {@linkplain
069 * EventQueueCollection#close() causes the <code>Consumer</code>
070 * supplied at construction time to stop receiving
071 * <code>Event</code>s}.</p>
072 *
073 * <p>Several {@code protected} methods in this class exist to make
074 * customization easier; none require overriding and their default
075 * behavior is usually just fine.</p>
076 *
077 * <h2>Thread Safety</h2>
078 *
079 * <p>Instances of this class are safe for concurrent use by multiple
080 * threads.</p>
081 *
082 * @param <T> a Kubernetes resource type
083 *
084 * @author <a href="https://about.me/lairdnelson"
085 * target="_parent">Laird Nelson</a>
086 *
087 * @see Reflector
088 *
089 * @see EventQueueCollection
090 *
091 * @see ResourceTrackingEventQueueConsumer
092 *
093 * @see #start()
094 *
095 * @see #close()
096 */
097@Immutable
098@ThreadSafe
099public class Controller<T extends HasMetadata> implements Closeable {
100
101
102  /*
103   * Instance fields.
104   */
105
106
107  /**
108   * A {@link Logger} used by this {@link Controller}.
109   *
110   * <p>This field is never {@code null}.</p>
111   *
112   * @see #createLogger()
113   */
114  protected final Logger logger;
115
116  /**
117   * The {@link Reflector} used by this {@link Controller} to mirror
118   * Kubernetes events.
119   *
120   * <p>This field is never {@code null}.</p>
121   */
122  private final Reflector<T> reflector;
123
124  /**
125   * The {@link EventQueueCollection} used by the {@link #reflector
126   * Reflector} and by the {@link Consumer} supplied at construction
127   * time.
128   *
129   * <p>This field is never {@code null}.</p>
130   *
131   * @see EventQueueCollection#add(Object, AbstractEvent.Type,
132   * HasMetadata)
133   *
134   * @see EventQueueCollection#replace(Collection, Object)
135   *
136   * @see EventQueueCollection#synchronize()
137   *
138   * @see EventQueueCollection#start(Consumer)
139   */
140  private final EventQueueCollection<T> eventCache;
141
142  /**
143   * A {@link Consumer} of {@link EventQueue}s that processes {@link
144   * Event}s produced, ultimately, by the {@link #reflector
145   * Reflector}.
146   *
147   * <p>This field is never {@code null}.</p>
148   */
149  private final Consumer<? super EventQueue<? extends T>> siphon;
150
151  
152  /*
153   * Constructors.
154   */
155
156
157  /**
158   * Creates a new {@link Controller} but does not {@linkplain
159   * #start() start it}.
160   *
161   * @param <X> a {@link Listable} and {@link VersionWatchable} that
162   * will be used by the embedded {@link Reflector}; must not be
163   * {@code null}
164   *
165   * @param operation a {@link Listable} and a {@link
166   * VersionWatchable} that produces Kubernetes events; must not be
167   * {@code null}
168   *
169   * @param siphon the {@link Consumer} that will process each {@link
170   * EventQueue} as it becomes ready; must not be {@code null}
171   *
172   * @exception NullPointerException if {@code operation} or {@code
173   * siphon} is {@code null}
174   *
175   * @see #Controller(Listable, ScheduledExecutorService, Duration,
176   * Map, Consumer)
177   *
178   * @see #start()
179   */
180  @SuppressWarnings("rawtypes")
181  public <X extends Listable<? extends KubernetesResourceList> & VersionWatchable<? extends Closeable, Watcher<T>>> Controller(final X operation,
182                                                                                                                               final Consumer<? super EventQueue<? extends T>> siphon) {
183    this(operation, null, null, null, siphon);
184  }
185
186  /**
187   * Creates a new {@link Controller} but does not {@linkplain
188   * #start() start it}.
189   *
190   * @param <X> a {@link Listable} and {@link VersionWatchable} that
191   * will be used by the embedded {@link Reflector}; must not be
192   * {@code null}
193   *
194   * @param operation a {@link Listable} and a {@link
195   * VersionWatchable} that produces Kubernetes events; must not be
196   * {@code null}
197   *
198   * @param knownObjects a {@link Map} containing the last known state
199   * of Kubernetes resources the embedded {@link EventQueueCollection}
200   * is caching events for; may be {@code null} if this {@link
201   * Controller} is not interested in tracking deletions of objects;
202   * if non-{@code null} <strong>will be synchronized on by this
203   * class</strong> during retrieval and traversal operations
204   *
205   * @param siphon the {@link Consumer} that will process each {@link
206   * EventQueue} as it becomes ready; must not be {@code null}
207   *
208   * @exception NullPointerException if {@code operation} or {@code
209   * siphon} is {@code null}
210   *
211   * @see #Controller(Listable, ScheduledExecutorService, Duration,
212   * Map, Consumer)
213   *
214   * @see #start()
215   */
216  @SuppressWarnings("rawtypes")
217  public <X extends Listable<? extends KubernetesResourceList> & VersionWatchable<? extends Closeable, Watcher<T>>> Controller(final X operation,
218                                                                                                                               final Map<Object, T> knownObjects,
219                                                                                                                               final Consumer<? super EventQueue<? extends T>> siphon) {
220    this(operation, null, null, knownObjects, siphon);
221  }
222
223  /**
224   * Creates a new {@link Controller} but does not {@linkplain
225   * #start() start it}.
226   *
227   * @param <X> a {@link Listable} and {@link VersionWatchable} that
228   * will be used by the embedded {@link Reflector}; must not be
229   * {@code null}
230   *
231   * @param operation a {@link Listable} and a {@link
232   * VersionWatchable} that produces Kubernetes events; must not be
233   * {@code null}
234   *
235   * @param synchronizationInterval a {@link Duration} representing
236   * the time in between one {@linkplain EventCache#synchronize()
237   * synchronization operation} and another; may be {@code null} in
238   * which case no synchronization will occur
239   *
240   * @param siphon the {@link Consumer} that will process each {@link
241   * EventQueue} as it becomes ready; must not be {@code null}
242   *
243   * @exception NullPointerException if {@code operation} or {@code
244   * siphon} is {@code null}
245   *
246   * @see #Controller(Listable, ScheduledExecutorService, Duration,
247   * Map, Consumer)
248   *
249   * @see #start()
250   */
251  @SuppressWarnings("rawtypes")
252  public <X extends Listable<? extends KubernetesResourceList> & VersionWatchable<? extends Closeable, Watcher<T>>> Controller(final X operation,
253                                                                                                                               final Duration synchronizationInterval,
254                                                                                                                               final Consumer<? super EventQueue<? extends T>> siphon) {
255    this(operation, null, synchronizationInterval, null, siphon);
256  }
257
258  /**
259   * Creates a new {@link Controller} but does not {@linkplain
260   * #start() start it}.
261   *
262   * @param <X> a {@link Listable} and {@link VersionWatchable} that
263   * will be used by the embedded {@link Reflector}; must not be
264   * {@code null}
265   *
266   * @param operation a {@link Listable} and a {@link
267   * VersionWatchable} that produces Kubernetes events; must not be
268   * {@code null}
269   *
270   * @param synchronizationInterval a {@link Duration} representing
271   * the time in between one {@linkplain EventCache#synchronize()
272   * synchronization operation} and another; may be {@code null} in
273   * which case no synchronization will occur
274   *
275   * @param knownObjects a {@link Map} containing the last known state
276   * of Kubernetes resources the embedded {@link EventQueueCollection}
277   * is caching events for; may be {@code null} if this {@link
278   * Controller} is not interested in tracking deletions of objects;
279   * if non-{@code null} <strong>will be synchronized on by this
280   * class</strong> during retrieval and traversal operations
281   *
282   * @param siphon the {@link Consumer} that will process each {@link
283   * EventQueue} as it becomes ready; must not be {@code null}
284   *
285   * @exception NullPointerException if {@code operation} or {@code
286   * siphon} is {@code null}
287   *
288   * @see #Controller(Listable, ScheduledExecutorService, Duration,
289   * Map, Consumer)
290   *
291   * @see #start()
292   */
293  @SuppressWarnings("rawtypes")
294  public <X extends Listable<? extends KubernetesResourceList> & VersionWatchable<? extends Closeable, Watcher<T>>> Controller(final X operation,
295                                                                                                                               final Duration synchronizationInterval,
296                                                                                                                               final Map<Object, T> knownObjects,
297                                                                                                                               final Consumer<? super EventQueue<? extends T>> siphon) {
298    this(operation, null, synchronizationInterval, knownObjects, siphon);
299  }
300
301  /**
302   * Creates a new {@link Controller} but does not {@linkplain
303   * #start() start it}.
304   *
305   * @param <X> a {@link Listable} and {@link VersionWatchable} that
306   * will be used by the embedded {@link Reflector}; must not be
307   * {@code null}
308   *
309   * @param operation a {@link Listable} and a {@link
310   * VersionWatchable} that produces Kubernetes events; must not be
311   * {@code null}
312   *
313   * @param synchronizationExecutorService the {@link
314   * ScheduledExecutorService} that will be passed to the {@link
315   * Reflector} constructor; may be {@code null} in which case a
316   * default {@link ScheduledExecutorService} may be used instead
317   *
318   * @param synchronizationInterval a {@link Duration} representing
319   * the time in between one {@linkplain EventCache#synchronize()
320   * synchronization operation} and another; may be {@code null} in
321   * which case no synchronization will occur
322   *
323   * @param knownObjects a {@link Map} containing the last known state
324   * of Kubernetes resources the embedded {@link EventQueueCollection}
325   * is caching events for; may be {@code null} if this {@link
326   * Controller} is not interested in tracking deletions of objects;
327   * if non-{@code null} <strong>will be synchronized on by this
328   * class</strong> during retrieval and traversal operations
329   *
330   * @param siphon the {@link Consumer} that will process each {@link
331   * EventQueue} as it becomes ready; must not be {@code null}
332   *
333   * @exception NullPointerException if {@code operation} or {@code
334   * siphon} is {@code null}
335   *
336   * @see #start()
337   */
338  @SuppressWarnings("rawtypes")
339  public <X extends Listable<? extends KubernetesResourceList> & VersionWatchable<? extends Closeable, Watcher<T>>> Controller(final X operation,
340                                                                                                                               final ScheduledExecutorService synchronizationExecutorService,
341                                                                                                                               final Duration synchronizationInterval,
342                                                                                                                               final Map<Object, T> knownObjects,
343                                                                                                                               final Consumer<? super EventQueue<? extends T>> siphon) {
344    super();
345    this.logger = this.createLogger();
346    if (this.logger == null) {
347      throw new IllegalStateException("createLogger() == null");
348    }
349    final String cn = this.getClass().getName();
350    final String mn = "<init>";
351    if (this.logger.isLoggable(Level.FINER)) {
352      this.logger.entering(cn, mn, new Object[] { operation, synchronizationExecutorService, synchronizationInterval, knownObjects, siphon });
353    }
354    this.siphon = Objects.requireNonNull(siphon);
355    this.eventCache = new ControllerEventQueueCollection(knownObjects);    
356    this.reflector = new ControllerReflector(operation, synchronizationExecutorService, synchronizationInterval);
357    if (this.logger.isLoggable(Level.FINER)) {
358      this.logger.exiting(cn, mn);
359    }
360  }
361
362
363  /*
364   * Instance methods.
365   */
366
367
368  /**
369   * Returns a {@link Logger} for use by this {@link Controller}.
370   *
371   * <p>This method never returns {@code null}.</p>
372   *
373   * <p>Overrides of this method must not return {@code null}.</p>
374   *
375   * @return a non-{@code null} {@link Logger}
376   */
377  protected Logger createLogger() {
378    return Logger.getLogger(this.getClass().getName());
379  }
380
381  /**
382   * {@linkplain EventQueueCollection#start(Consumer) Starts the
383   * embedded <code>EventQueueCollection</code> consumption machinery}
384   * and then {@linkplain Reflector#start() starts the embedded
385   * <code>Reflector</code>}.
386   *
387   * @see EventQueueCollection#start(Consumer)
388   *
389   * @see Reflector#start()
390   */
391  @NonBlocking
392  public final void start() {
393    final String cn = this.getClass().getName();
394    final String mn = "start";
395    if (this.logger.isLoggable(Level.FINER)) {
396      this.logger.entering(cn, mn);
397    }
398    if (this.logger.isLoggable(Level.INFO)) {
399      this.logger.logp(Level.INFO, cn, mn, "Starting {0}", this.siphon);
400    }
401    final Future<?> siphonTask = this.eventCache.start(this.siphon);
402    assert siphonTask != null;
403    if (this.logger.isLoggable(Level.INFO)) {
404      this.logger.logp(Level.INFO, cn, mn, "Starting {0}", this.reflector);
405    }
406    try {
407      this.reflector.start();
408    } catch (final RuntimeException runtimeException) {
409      try {        
410        this.reflector.close();
411      } catch (final IOException suppressMe) {
412        runtimeException.addSuppressed(suppressMe);
413      }
414      siphonTask.cancel(true);
415      assert siphonTask.isDone();
416      try {
417        this.eventCache.close();
418      } catch (final RuntimeException suppressMe) {
419        runtimeException.addSuppressed(suppressMe);
420      }
421      throw runtimeException;
422    }
423    if (this.logger.isLoggable(Level.FINER)) {
424      this.logger.exiting(cn, mn);
425    }
426  }
427
428  /**
429   * {@linkplain Reflector#close() Closes the embedded
430   * <code>Reflector</code>} and then {@linkplain
431   * EventQueueCollection#close() closes the embedded
432   * <code>EventQueueCollection</code>}, handling exceptions
433   * appropriately.
434   *
435   * @exception IOException if the {@link Reflector} could not
436   * {@linkplain Reflector#close() close} properly
437   *
438   * @see Reflector#close()
439   *
440   * @see EventQueueCollection#close()
441   */
442  @Override
443  public final void close() throws IOException {
444    final String cn = this.getClass().getName();
445    final String mn = "close";
446    if (this.logger.isLoggable(Level.FINER)) {
447      this.logger.entering(cn, mn);
448    }
449    Exception throwMe = null;    
450    try {
451      if (this.logger.isLoggable(Level.INFO)) {
452        this.logger.logp(Level.INFO, cn, mn, "Closing {0}", this.reflector);
453      }
454      this.reflector.close();
455    } catch (final Exception everything) {
456      throwMe = everything;
457    }
458
459    try {
460      if (this.logger.isLoggable(Level.INFO)) {
461        this.logger.logp(Level.INFO, cn, mn, "Closing {0}", this.eventCache);
462      }
463      this.eventCache.close();
464    } catch (final RuntimeException runtimeException) {
465      if (throwMe == null) {
466        throw runtimeException;
467      }
468      throwMe.addSuppressed(runtimeException);
469    }
470
471    if (throwMe instanceof IOException) {
472      throw (IOException)throwMe;
473    } else if (throwMe instanceof RuntimeException) {
474      throw (RuntimeException)throwMe;
475    } else if (throwMe != null) {
476      throw new IllegalStateException(throwMe.getMessage(), throwMe);
477    }
478
479    if (this.logger.isLoggable(Level.FINER)) {
480      this.logger.exiting(cn, mn);
481    }
482  }
483
484  /**
485   * Returns if the embedded {@link Reflector} should {@linkplain
486   * Reflector#shouldSynchronize() synchronize}.
487   *
488   * <p>This implementation returns {@code true}.</p>
489   *
490   * @return {@code true} if the embedded {@link Reflector} should
491   * {@linkplain Reflector#shouldSynchronize() synchronize}; {@code
492   * false} otherwise
493   */
494  protected boolean shouldSynchronize() {
495    final String cn = this.getClass().getName();
496    final String mn = "shouldSynchronize";
497    if (this.logger.isLoggable(Level.FINER)) {
498      this.logger.entering(cn, mn);
499    }
500    final boolean returnValue = true;
501    if (this.logger.isLoggable(Level.FINER)) {
502      this.logger.exiting(cn, mn, Boolean.valueOf(returnValue));
503    }
504    return returnValue;
505  }
506
507  /**
508   * Invoked after the embedded {@link Reflector} {@linkplain
509   * Reflector#onClose() closes}.
510   *
511   * <p>This implementation does nothing.</p>
512   *
513   * @see Reflector#close()
514   *
515   * @see Reflector#onClose()
516   */
517  protected void onClose() {
518
519  }
520
521  /**
522   * Returns a key that can be used to identify the supplied {@link
523   * HasMetadata}.
524   *
525   * <p>This method never returns {@code null}.</p>
526   *
527   * <p>Overrides of this method must not return {@code null}.</p>
528   *
529   * <p>The default implementation of this method returns the return
530   * value of invoking the {@link HasMetadatas#getKey(HasMetadata)}
531   * method.</p>
532   *
533   * @param resource the Kubernetes resource for which a key is
534   * desired; must not be {@code null}
535   *
536   * @return a non-{@code null} key for the supplied {@link
537   * HasMetadata}
538   *
539   * @exception NullPointerException if {@code resource} is {@code
540   * null}
541   */
542  protected Object getKey(final T resource) {
543    final String cn = this.getClass().getName();
544    final String mn = "getKey";
545    if (this.logger.isLoggable(Level.FINER)) {
546      this.logger.entering(cn, mn, resource);
547    }
548    final Object returnValue = HasMetadatas.getKey(Objects.requireNonNull(resource));
549    if (this.logger.isLoggable(Level.FINER)) {
550      this.logger.exiting(cn, mn, returnValue);
551    }
552    return returnValue;
553  }
554
555  /**
556   * Creates a new {@link Event} when invoked.
557   *
558   * <p>This method never returns {@code null}.</p>
559   *
560   * <p>Overrides of this method must not return {@code null}.</p>
561   *
562   * <p>Overrides of this method must return a new {@link Event} or
563   * subclass with each invocation.</p>
564   *
565   * @param source the source of the new {@link Event}; must not be
566   * {@code null}
567   *
568   * @param eventType the {@link Event.Type} for the new {@link
569   * Event}; must not be {@code null}
570   *
571   * @param resource the {@link HasMetadata} that the new {@link
572   * Event} concerns; must not be {@code null}
573   *
574   * @return a new, non-{@code null} {@link Event}
575   *
576   * @exception NullPointerException if any of the parameters is
577   * {@code null}
578   */
579  protected Event<T> createEvent(final Object source, final Event.Type eventType, final T resource) {
580    final String cn = this.getClass().getName();
581    final String mn = "createEvent";
582    if (this.logger.isLoggable(Level.FINER)) {
583      this.logger.entering(cn, mn, new Object[] { source, eventType, resource });
584    }
585    final Event<T> returnValue = new Event<>(Objects.requireNonNull(source), Objects.requireNonNull(eventType), null, Objects.requireNonNull(resource));
586    if (this.logger.isLoggable(Level.FINER)) {
587      this.logger.exiting(cn, mn, returnValue);
588    }
589    return returnValue;
590  }
591
592  /**
593   * Creates a new {@link EventQueue} when invoked.
594   *
595   * <p>This method never returns {@code null}.</p>
596   *
597   * <p>Overrides of this method must not return {@code null}.</p>
598   *
599   * <p>Overrides of this method must return a new {@link EventQueue}
600   * or subclass with each invocation.</p>
601   *
602   * @param key the key to create the new {@link EventQueue} with;
603   * must not be {@code null}
604   *
605   * @return a new, non-{@code null} {@link EventQueue}
606   *
607   * @exception NullPointerException if {@code key} is {@code null}
608   */
609  protected EventQueue<T> createEventQueue(final Object key) {
610    final String cn = this.getClass().getName();
611    final String mn = "createEventQueue";
612    if (this.logger.isLoggable(Level.FINER)) {
613      this.logger.entering(cn, mn, key);
614    }
615    final EventQueue<T> returnValue = new EventQueue<>(key);
616    if (this.logger.isLoggable(Level.FINER)) {
617      this.logger.exiting(cn, mn, returnValue);
618    }
619    return returnValue;
620  }
621
622
623  /*
624   * Inner and nested classes.
625   */
626
627
628  /**
629   * An {@link EventQueueCollection} that delegates its overridable
630   * methods to their equivalents in the {@link Controller} class.
631   *
632   * @author <a href="https://about.me/lairdnelson"
633   * target="_parent">Laird Nelson</a>
634   *
635   * @see EventQueueCollection
636   *
637   * @see EventCache
638   */
639  private final class ControllerEventQueueCollection extends EventQueueCollection<T> {
640
641
642    /*
643     * Constructors.
644     */
645
646    
647    private ControllerEventQueueCollection(final Map<?, ? extends T> knownObjects) {
648      super(knownObjects);
649    }
650
651
652    /*
653     * Instance methods.
654     */
655
656    
657    @Override
658    protected final Event<T> createEvent(final Object source, final Event.Type eventType, final T resource) {
659      return Controller.this.createEvent(source, eventType, resource);
660    }
661    
662    @Override
663    protected final EventQueue<T> createEventQueue(final Object key) {
664      return Controller.this.createEventQueue(key);
665    }
666    
667    @Override
668    protected final Object getKey(final T resource) {
669      return Controller.this.getKey(resource);
670    }
671    
672  }
673
674  
675  /**
676   * A {@link Reflector} that delegates its overridable
677   * methods to their equivalents in the {@link Controller} class.
678   *
679   * @author <a href="https://about.me/lairdnelson"
680   * target="_parent">Laird Nelson</a>
681   *
682   * @see Reflector
683   */
684  private final class ControllerReflector extends Reflector<T> {
685
686
687    /*
688     * Constructors.
689     */
690
691    
692    @SuppressWarnings("rawtypes")
693    private <X extends Listable<? extends KubernetesResourceList> & VersionWatchable<? extends Closeable, Watcher<T>>> ControllerReflector(final X operation,
694                                                                                                                                           final ScheduledExecutorService synchronizationExecutorService,
695                                                                                                                                           final Duration synchronizationInterval) {
696      super(operation, Controller.this.eventCache, synchronizationExecutorService, synchronizationInterval);
697    }
698
699
700    /*
701     * Instance methods.
702     */
703    
704    
705    @Override
706    protected final boolean shouldSynchronize() {
707      return Controller.this.shouldSynchronize();
708    }
709
710    @Override
711    protected final void onClose() {
712      Controller.this.onClose();
713    }
714  }
715  
716}