Fix wait for schema in BindingToNormalizedNodeCodec
[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
9 package org.opendaylight.mdsal.binding.dom.adapter;
10
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.util.concurrent.SettableFuture;
14 import java.net.URI;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.Date;
18 import java.util.LinkedHashSet;
19 import java.util.Set;
20 import java.util.concurrent.ExecutionException;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.TimeoutException;
23 import java.util.function.Predicate;
24 import javax.annotation.concurrent.GuardedBy;
25 import org.opendaylight.mdsal.binding.generator.util.BindingRuntimeContext;
26 import org.opendaylight.yangtools.yang.binding.Augmentation;
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(FutureSchema.this.duration, FutureSchema.this.unit);
52                 return true;
53             } catch (final InterruptedException | ExecutionException e) {
54                 throw new RuntimeException(e);
55             } catch (final TimeoutException e) {
56                 return false;
57             } finally {
58                 synchronized (FutureSchema.this.postponedOperations) {
59                     FutureSchema.this.postponedOperations.remove(this);
60                 }
61             }
62         }
63
64         final void unlockIfPossible(final BindingRuntimeContext context) {
65             if (!schemaPromise.isDone() && test(context)) {
66                 schemaPromise.set(null);
67             }
68         }
69
70         final void cancel() {
71             schemaPromise.cancel(true);
72         }
73     }
74
75     @GuardedBy("postponedOperations")
76     private final Set<FutureSchemaPredicate> postponedOperations = new LinkedHashSet<>();
77     private final long duration;
78     private final TimeUnit unit;
79
80     private volatile BindingRuntimeContext runtimeContext;
81
82     FutureSchema(final long time, final TimeUnit unit) {
83         this.duration = time;
84         this.unit = requireNonNull(unit);
85     }
86
87     static FutureSchema create(final long time, final TimeUnit unit, final boolean waitEnabled) {
88         return waitEnabled ? new Waiting(time, unit) : new NonWaiting(time, unit);
89     }
90
91     BindingRuntimeContext runtimeContext() {
92         final BindingRuntimeContext localRuntimeContext = runtimeContext;
93         if (localRuntimeContext != null) {
94             return localRuntimeContext;
95         }
96
97         if (waitForSchema(Collections.emptyList())) {
98             return runtimeContext;
99         }
100
101         throw new IllegalStateException("No SchemaContext is available");
102     }
103
104     void onRuntimeContextUpdated(final BindingRuntimeContext context) {
105         synchronized (postponedOperations) {
106             runtimeContext = context;
107             for (final FutureSchemaPredicate op : postponedOperations) {
108                 op.unlockIfPossible(context);
109             }
110         }
111     }
112
113     long getDuration() {
114         return duration;
115     }
116
117     TimeUnit getUnit() {
118         return unit;
119     }
120
121     @Override
122     public void close() {
123         synchronized (postponedOperations) {
124             postponedOperations.forEach(FutureSchemaPredicate::cancel);
125         }
126     }
127
128     boolean waitForSchema(final URI namespace, final Date revision) {
129         return addPostponedOpAndWait(new FutureSchemaPredicate() {
130             @Override
131             public boolean test(final BindingRuntimeContext input) {
132                 return input.getSchemaContext().findModuleByNamespaceAndRevision(namespace, revision) != null;
133             }
134         });
135     }
136
137     boolean waitForSchema(final Collection<Class<?>> bindingClasses) {
138         return addPostponedOpAndWait(new FutureSchemaPredicate() {
139             @Override
140             public boolean test(final BindingRuntimeContext context) {
141                 return bindingClasses.stream().allMatch(clz -> {
142                     if (Augmentation.class.isAssignableFrom(clz)) {
143                         return context.getAugmentationDefinition(clz) != null;
144                     }
145
146                     return context.getSchemaDefinition(clz) != null;
147                 });
148             }
149         });
150     }
151
152     boolean addPostponedOpAndWait(final FutureSchemaPredicate postponedOp) {
153         synchronized (postponedOperations) {
154             postponedOperations.add(postponedOp);
155
156             // If the runtimeContext changed, this op may now be satisfied so check it.
157             final BindingRuntimeContext context = runtimeContext;
158             if (context != null) {
159                 postponedOp.unlockIfPossible(context);
160             }
161         }
162
163         return postponedOp.waitForSchema();
164     }
165 }