ee2864878d1c0a7b5e2bdfb27ad49344d79b46fc
[controller.git] / opendaylight / config / yang-store-impl / src / main / java / org / opendaylight / controller / config / yang / store / impl / ExtenderYangTracker.java
1 /*
2  * Copyright (c) 2013 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.controller.config.yang.store.impl;
9
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.net.URL;
13 import java.util.*;
14
15 import org.opendaylight.controller.config.yang.store.api.YangStoreException;
16 import org.opendaylight.controller.config.yang.store.api.YangStoreListenerRegistration;
17 import org.opendaylight.controller.config.yang.store.api.YangStoreService;
18 import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot;
19 import org.opendaylight.controller.config.yang.store.spi.YangStoreListener;
20 import org.osgi.framework.Bundle;
21 import org.osgi.framework.BundleContext;
22 import org.osgi.framework.BundleEvent;
23 import org.osgi.util.tracker.BundleTracker;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 import com.google.common.annotations.VisibleForTesting;
28 import com.google.common.base.Function;
29 import com.google.common.base.Optional;
30 import com.google.common.base.Preconditions;
31 import com.google.common.collect.Collections2;
32 import com.google.common.collect.HashMultimap;
33 import com.google.common.collect.Multimap;
34 import com.google.common.collect.Sets;
35
36 public class ExtenderYangTracker extends BundleTracker<Object> implements
37         YangStoreService {
38
39     private static final Logger logger = LoggerFactory
40             .getLogger(ExtenderYangTracker.class);
41
42     private final Multimap<Bundle, URL> bundlesToYangURLs = HashMultimap
43             .create();
44     private final YangStoreCache cache = new YangStoreCache();
45     private final MbeParser mbeParser;
46     private final List<YangStoreListener> listeners = new ArrayList<>();
47
48     public ExtenderYangTracker(BundleContext context) {
49         this(context, new MbeParser());
50
51     }
52
53     @VisibleForTesting
54     ExtenderYangTracker(BundleContext context, MbeParser mbeParser) {
55         super(context, Bundle.ACTIVE, null);
56         this.mbeParser = mbeParser;
57         logger.trace("Registered as extender with context {}", context);
58     }
59
60     @Override
61     public Object addingBundle(Bundle bundle, BundleEvent event) {
62
63         // Ignore system bundle:
64         // system bundle might have config-api on classpath &&
65         // config-api contains yang files =>
66         // system bundle might contain yang files from that bundle
67         if (bundle.getBundleId() == 0)
68             return bundle;
69
70         Enumeration<URL> yangURLs = bundle.findEntries("META-INF/yang", "*.yang", false);
71         if (yangURLs != null) {
72             synchronized (this) {
73                 while (yangURLs.hasMoreElements()) {
74                     URL url = yangURLs.nextElement();
75                     logger.debug("Bundle {} found yang file {}", bundle, url);
76                     bundlesToYangURLs.put(bundle, url);
77                 }
78                 Collection<URL> urls = bundlesToYangURLs.get(bundle);
79                 notifyListeners(urls, true);
80             }
81         }
82         return bundle;
83     }
84
85     private void notifyListeners(Collection<URL> urls, boolean adding) {
86         if (urls.size() > 0) {
87             RuntimeException potential = new RuntimeException("Error while notifying listeners");
88             for (YangStoreListener listener : listeners) {
89                 try {
90                     if (adding) {
91                         listener.onAddedYangURL(urls);
92                     } else {
93                         listener.onRemovedYangURL(urls);
94                     }
95                 } catch(RuntimeException e) {
96                     potential.addSuppressed(e);
97                 }
98             }
99             if (potential.getSuppressed().length > 0) {
100                 throw potential;
101             }
102         }
103     }
104
105     @Override
106     public synchronized void removedBundle(Bundle bundle, BundleEvent event, Object object) {
107         Collection<URL> urls = bundlesToYangURLs.removeAll(bundle);
108         if (urls.size() > 0) {
109             logger.debug("Removed following yang URLs {} because of removed bundle {}", urls, bundle);
110             notifyListeners(urls, false);
111         }
112     }
113
114     @Override
115     public synchronized YangStoreSnapshot getYangStoreSnapshot()
116             throws YangStoreException {
117         Optional<YangStoreSnapshot> yangStoreOpt = cache
118                 .getCachedYangStore(bundlesToYangURLs);
119         if (yangStoreOpt.isPresent()) {
120             logger.debug("Returning cached yang store {}", yangStoreOpt.get());
121             return yangStoreOpt.get();
122         }
123
124         try {
125             YangStoreSnapshot yangStoreSnapshot = mbeParser
126                     .parseYangFiles(fromUrlsToInputStreams());
127             logger.debug(
128                     "{} module entries parsed successfully from {} yang files",
129                     yangStoreSnapshot.countModuleMXBeanEntries(),
130                     bundlesToYangURLs.values().size());
131             cache.cacheYangStore(bundlesToYangURLs, yangStoreSnapshot);
132
133             return yangStoreSnapshot;
134         } catch (RuntimeException e) {
135             logger.warn(
136                     "Unable to parse yang files, yang files that were picked up so far: {}",
137                     bundlesToYangURLs, e);
138             throw new YangStoreException("Unable to parse yang files", e);
139         }
140     }
141
142     private Collection<InputStream> fromUrlsToInputStreams() {
143         return Collections2.transform(bundlesToYangURLs.values(),
144                 new Function<URL, InputStream>() {
145
146                     @Override
147                     public InputStream apply(URL url) {
148                         try {
149                             return url.openStream();
150                         } catch (IOException e) {
151                             logger.warn("Unable to open stream from {}", url);
152                             throw new IllegalStateException(
153                                     "Unable to open stream from " + url, e);
154                         }
155                     }
156                 });
157     }
158
159     @Override
160     public synchronized YangStoreListenerRegistration registerListener(final YangStoreListener listener) {
161         listeners.add(listener);
162         return new YangStoreListenerRegistration() {
163             @Override
164             public void close() {
165                 listeners.remove(listener);
166             }
167         };
168     }
169
170     private static final class YangStoreCache {
171
172         Set<URL> cachedUrls;
173         YangStoreSnapshot cachedYangStoreSnapshot;
174
175         Optional<YangStoreSnapshot> getCachedYangStore(
176                 Multimap<Bundle, URL> bundlesToYangURLs) {
177             Set<URL> urls = setFromMultimapValues(bundlesToYangURLs);
178             if (cachedUrls != null && cachedUrls.equals(urls)) {
179                 Preconditions.checkState(cachedYangStoreSnapshot != null);
180                 return Optional.of(cachedYangStoreSnapshot);
181             }
182             return Optional.absent();
183         }
184
185         private static Set<URL> setFromMultimapValues(
186                 Multimap<Bundle, URL> bundlesToYangURLs) {
187             Set<URL> urls = Sets.newHashSet(bundlesToYangURLs.values());
188             Preconditions.checkState(bundlesToYangURLs.size() == urls.size());
189             return urls;
190         }
191
192         void cacheYangStore(Multimap<Bundle, URL> urls,
193                 YangStoreSnapshot yangStoreSnapshot) {
194             this.cachedUrls = setFromMultimapValues(urls);
195             this.cachedYangStoreSnapshot = yangStoreSnapshot;
196         }
197
198     }
199 }