03248a75bca49edc341938203f473e9a9778e9f9
[mdsal.git] / binding / mdsal-binding-dom-adapter / src / main / java / org / opendaylight / mdsal / binding / dom / adapter / FutureSchema.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.binding.dom.adapter;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.util.concurrent.SettableFuture;
13 import java.util.Collection;
14 import java.util.Collections;
15 import java.util.LinkedHashSet;
16 import java.util.Set;
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.TimeUnit;
19 import java.util.concurrent.TimeoutException;
20 import java.util.function.Predicate;
21 import org.checkerframework.checker.lock.qual.GuardedBy;
22 import org.opendaylight.mdsal.binding.runtime.api.BindingRuntimeContext;
23 import org.opendaylight.yangtools.yang.binding.Augmentation;
24 import org.opendaylight.yangtools.yang.common.QNameModule;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27
28 abstract class FutureSchema implements AutoCloseable {
29     private static final class Waiting extends FutureSchema {
30         Waiting(final long time, final TimeUnit unit) {
31             super(time, unit);
32         }
33     }
34
35     private static final class NonWaiting extends FutureSchema {
36         NonWaiting(final long time, final TimeUnit unit) {
37             super(time, unit);
38         }
39
40         @Override
41         boolean addPostponedOpAndWait(final FutureSchemaPredicate postponedOp) {
42             return false;
43         }
44     }
45
46     private abstract class FutureSchemaPredicate implements Predicate<BindingRuntimeContext> {
47         private final SettableFuture<Void> schemaPromise = SettableFuture.create();
48
49         final boolean waitForSchema() {
50             try {
51                 schemaPromise.get(duration, unit);
52                 return true;
53             } catch (final InterruptedException | ExecutionException e) {
54                 throw new IllegalStateException(e);
55             } catch (final TimeoutException e) {
56                 LOG.trace("Wait for {} timed out", schemaPromise, e);
57                 return false;
58             } finally {
59                 synchronized (postponedOperations) {
60                     postponedOperations.remove(this);
61                 }
62             }
63         }
64
65         final void unlockIfPossible(final BindingRuntimeContext context) {
66             if (!schemaPromise.isDone() && test(context)) {
67                 schemaPromise.set(null);
68             }
69         }
70
71         final void cancel() {
72             schemaPromise.cancel(true);
73         }
74     }
75
76     private static final Logger LOG = LoggerFactory.getLogger(FutureSchema.class);
77
78     @GuardedBy("postponedOperations")
79     private final Set<FutureSchemaPredicate> postponedOperations = new LinkedHashSet<>();
80     private final long duration;
81     private final TimeUnit unit;
82
83     private volatile BindingRuntimeContext runtimeContext;
84
85     FutureSchema(final long time, final TimeUnit unit) {
86         duration = time;
87         this.unit = requireNonNull(unit);
88     }
89
90     static FutureSchema create(final long time, final TimeUnit unit, final boolean waitEnabled) {
91         return waitEnabled ? new Waiting(time, unit) : new NonWaiting(time, unit);
92     }
93
94     BindingRuntimeContext runtimeContext() {
95         final BindingRuntimeContext localRuntimeContext = runtimeContext;
96         if (localRuntimeContext != null) {
97             return localRuntimeContext;
98         }
99
100         if (waitForSchema(Collections.emptyList())) {
101             return runtimeContext;
102         }
103
104         throw new IllegalStateException("No SchemaContext is available");
105     }
106
107     void onRuntimeContextUpdated(final BindingRuntimeContext context) {
108         synchronized (postponedOperations) {
109             runtimeContext = context;
110             for (final FutureSchemaPredicate op : postponedOperations) {
111                 op.unlockIfPossible(context);
112             }
113         }
114     }
115
116     long getDuration() {
117         return duration;
118     }
119
120     TimeUnit getUnit() {
121         return unit;
122     }
123
124     @Override
125     public void close() {
126         synchronized (postponedOperations) {
127             postponedOperations.forEach(FutureSchemaPredicate::cancel);
128         }
129     }
130
131     boolean waitForSchema(final QNameModule module) {
132         return addPostponedOpAndWait(new FutureSchemaPredicate() {
133             @Override
134             public boolean test(final BindingRuntimeContext input) {
135                 return input.getEffectiveModelContext().findModule(module).isPresent();
136             }
137         });
138     }
139
140     boolean waitForSchema(final Collection<Class<?>> bindingClasses) {
141         return addPostponedOpAndWait(new FutureSchemaPredicate() {
142             @Override
143             public boolean test(final BindingRuntimeContext context) {
144                 return bindingClasses.stream().allMatch(clz -> {
145                     if (Augmentation.class.isAssignableFrom(clz)) {
146                         return context.getAugmentationDefinition(clz.asSubclass(Augmentation.class)) != null;
147                     }
148
149                     return context.getSchemaDefinition(clz) != null;
150                 });
151             }
152         });
153     }
154
155     boolean addPostponedOpAndWait(final FutureSchemaPredicate postponedOp) {
156         synchronized (postponedOperations) {
157             postponedOperations.add(postponedOp);
158
159             // If the runtimeContext changed, this op may now be satisfied so check it.
160             final BindingRuntimeContext context = runtimeContext;
161             if (context != null) {
162                 postponedOp.unlockIfPossible(context);
163             }
164         }
165
166         return postponedOp.waitForSchema();
167     }
168 }