BUG-4367: Use SchemaSourceProvider to retrieve sources for yang
[controller.git] / opendaylight / config / config-manager-facade-xml / src / main / java / org / opendaylight / controller / config / facade / xml / osgi / YangStoreService.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.controller.config.facade.xml.osgi;
10
11 import com.google.common.base.Function;
12 import com.google.common.collect.Collections2;
13 import com.google.common.collect.Sets;
14 import java.lang.ref.SoftReference;
15 import java.util.Collections;
16 import java.util.HashSet;
17 import java.util.Map;
18 import java.util.Set;
19 import java.util.concurrent.ExecutorService;
20 import java.util.concurrent.Executors;
21 import java.util.concurrent.ThreadFactory;
22 import java.util.concurrent.atomic.AtomicReference;
23 import javax.annotation.Nullable;
24 import org.opendaylight.controller.config.util.capability.Capability;
25 import org.opendaylight.controller.config.util.capability.ModuleListener;
26 import org.opendaylight.controller.config.util.capability.YangModuleCapability;
27 import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry;
28 import org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext;
29 import org.opendaylight.yangtools.yang.common.QName;
30 import org.opendaylight.yangtools.yang.model.api.Module;
31 import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier;
32 import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider;
33 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
34 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceProvider;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 public class YangStoreService implements YangStoreContext {
39
40     private static final Logger LOG = LoggerFactory.getLogger(YangStoreService.class);
41
42     /**
43      * This is a rather interesting locking model. We need to guard against both the
44      * cache expiring from GC and being invalidated by schema context change. The
45      * context can change while we are doing processing, so we do not want to block
46      * it, so no synchronization can happen on the methods.
47      *
48      * So what we are doing is the following:
49      *
50      * We synchronize with GC as usual, using a SoftReference.
51      *
52      * The atomic reference is used to synchronize with {@link #refresh(org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext)}, e.g. when
53      * refresh happens, it will push a SoftReference(null), e.g. simulate the GC. Now
54      * that may happen while the getter is already busy acting on the old schema context,
55      * so it needs to understand that a refresh has happened and retry. To do that, it
56      * attempts a CAS operation -- if it fails, in knows that the SoftReference has
57      * been replaced and thus it needs to retry.
58      *
59      * Note that {@link #getYangStoreSnapshot()} will still use synchronize() internally
60      * to stop multiple threads doing the same work.
61      */
62     private final AtomicReference<SoftReference<YangStoreSnapshot>> ref =
63             new AtomicReference<>(new SoftReference<YangStoreSnapshot>(null));
64
65     private final AtomicReference<SoftReference<BindingRuntimeContext>> refBindingContext =
66             new AtomicReference<>(new SoftReference<BindingRuntimeContext>(null));
67
68     private final SchemaContextProvider schemaContextProvider;
69     private final SchemaSourceProvider<YangTextSchemaSource> sourceProvider;
70
71     private final ExecutorService notificationExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
72         @Override
73         public Thread newThread(final Runnable r) {
74             return new Thread(r, "yangstore-capability-notifications");
75         }
76     });
77
78     private final Set<ModuleListener> listeners = Collections.synchronizedSet(new HashSet<ModuleListener>());
79
80     public YangStoreService(final SchemaContextProvider schemaContextProvider,
81         final SchemaSourceProvider<YangTextSchemaSource> sourceProvider) {
82         this.schemaContextProvider = schemaContextProvider;
83         this.sourceProvider = sourceProvider;
84     }
85
86     private synchronized YangStoreContext getYangStoreSnapshot() {
87         SoftReference<YangStoreSnapshot> r = ref.get();
88         YangStoreSnapshot ret = r.get();
89
90         while (ret == null) {
91             // We need to be compute a new value
92             // TODO sourceProvider is not a snapshot
93             ret = new YangStoreSnapshot(schemaContextProvider.getSchemaContext(), refBindingContext.get().get(), sourceProvider);
94
95             if (!ref.compareAndSet(r, new SoftReference<>(ret))) {
96                 LOG.debug("Concurrent refresh detected, recomputing snapshot");
97                 r = ref.get();
98                 ret = null;
99             }
100         }
101
102         return ret;
103     }
104
105     public YangStoreContext getCurrentSnapshot() {
106         return getYangStoreSnapshot();
107     }
108
109     @Override
110     public Map<String, Map<String, ModuleMXBeanEntry>> getModuleMXBeanEntryMap() {
111         return getYangStoreSnapshot().getModuleMXBeanEntryMap();
112     }
113
114     @Override
115     public Map<QName, Map<String, ModuleMXBeanEntry>> getQNamesToIdentitiesToModuleMXBeanEntries() {
116         return getYangStoreSnapshot().getQNamesToIdentitiesToModuleMXBeanEntries();
117     }
118
119     @Override
120     public Set<Module> getModules() {
121         return getYangStoreSnapshot().getModules();
122     }
123
124     @Override
125     public String getModuleSource(final ModuleIdentifier moduleIdentifier) {
126         return getYangStoreSnapshot().getModuleSource(moduleIdentifier);
127     }
128
129     @Override
130     public EnumResolver getEnumResolver() {
131         return getYangStoreSnapshot().getEnumResolver();
132     }
133
134     public void refresh(final BindingRuntimeContext runtimeContext) {
135         final YangStoreSnapshot previous = ref.get().get();
136         ref.set(new SoftReference<YangStoreSnapshot>(null));
137         refBindingContext.set(new SoftReference<>(runtimeContext));
138         notificationExecutor.submit(new CapabilityChangeNotifier(previous));
139     }
140
141     public AutoCloseable registerModuleListener(final ModuleListener listener) {
142         YangStoreContext context = ref.get().get();
143
144         if (context == null) {
145             context = getYangStoreSnapshot();
146         }
147
148         this.listeners.add(listener);
149         listener.onCapabilitiesChanged(toCapabilities(context.getModules(), context), Collections.<Capability>emptySet());
150
151         return new AutoCloseable() {
152             @Override
153             public void close() throws Exception {
154                 YangStoreService.this.listeners.remove(listener);
155             }
156         };
157     }
158
159     private Set<Capability> toCapabilities(final Set<Module> modules, final YangStoreContext current) {
160         return Sets.newHashSet(Collections2.transform(modules, new Function<Module, Capability>() {
161             @Nullable @Override public Capability apply(final Module input) {
162                 return new YangModuleCapability(input, current.getModuleSource(input));
163             }
164         }));
165     }
166
167     private final class CapabilityChangeNotifier implements Runnable {
168
169         private final YangStoreSnapshot previous;
170
171         public CapabilityChangeNotifier(final YangStoreSnapshot previous) {
172             this.previous = previous;
173         }
174
175         @Override
176         public void run() {
177             final YangStoreContext current = getYangStoreSnapshot();
178
179             if(!current.equals(previous)) {
180                 final Set<Module> removed = Sets.difference(previous.getModules(), current.getModules());
181                 final Set<Module> added = Sets.difference(current.getModules(), previous.getModules());
182
183                 for (final ModuleListener listener : listeners) {
184                     listener.onCapabilitiesChanged(toCapabilities(added, current), toCapabilities(removed, current));
185                 }
186             }
187         }
188
189     }
190 }