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