Rework binding component instantiation
[mdsal.git] / dom / mdsal-dom-schema-osgi / src / main / java / org / opendaylight / mdsal / dom / schema / osgi / impl / YangModuleInfoScanner.java
1 /*
2  * Copyright (c) 2017, 2020 PANTHEON.tech, s.r.o. 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.dom.schema.osgi.impl;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.collect.ImmutableList;
13 import com.google.common.io.Resources;
14 import java.io.IOException;
15 import java.lang.reflect.Constructor;
16 import java.lang.reflect.InvocationTargetException;
17 import java.net.URL;
18 import java.nio.charset.StandardCharsets;
19 import java.util.ArrayList;
20 import java.util.List;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.opendaylight.yangtools.concepts.ObjectRegistration;
23 import org.opendaylight.yangtools.yang.binding.YangModelBindingProvider;
24 import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
25 import org.osgi.framework.Bundle;
26 import org.osgi.framework.BundleContext;
27 import org.osgi.framework.BundleEvent;
28 import org.osgi.framework.Constants;
29 import org.osgi.util.tracker.BundleTracker;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 /**
34  * Tracks bundles and attempts to retrieve YangModuleInfo, which is then fed into ModuleInfoRegistry.
35  */
36 final class YangModuleInfoScanner extends BundleTracker<List<ObjectRegistration<YangModuleInfo>>> {
37     private static final Logger LOG = LoggerFactory.getLogger(YangModuleInfoScanner.class);
38     // FIXME: this should be in a place shared with maven-sal-api-gen-plugin
39     private static final String MODULE_INFO_PROVIDER_PATH_PREFIX = "META-INF/services/";
40
41     private static final String YANG_MODLE_BINDING_PROVIDER_SERVICE = MODULE_INFO_PROVIDER_PATH_PREFIX
42             + YangModelBindingProvider.class.getName();
43
44     private final YangModuleInfoRegistry moduleInfoRegistry;
45
46     YangModuleInfoScanner(final BundleContext context, final YangModuleInfoRegistry moduleInfoRegistry) {
47         super(context, Bundle.RESOLVED | Bundle.STARTING | Bundle.STOPPING | Bundle.ACTIVE, null);
48         this.moduleInfoRegistry = requireNonNull(moduleInfoRegistry);
49     }
50
51     @Override
52     @SuppressWarnings("checkstyle:illegalCatch")
53     public List<ObjectRegistration<YangModuleInfo>> addingBundle(final Bundle bundle, final BundleEvent event) {
54         if (bundle.getBundleId() == Constants.SYSTEM_BUNDLE_ID) {
55             LOG.debug("Ignoring system bundle {}", bundle);
56             return ImmutableList.of();
57         }
58
59         final URL resource = bundle.getEntry(YANG_MODLE_BINDING_PROVIDER_SERVICE);
60         if (resource == null) {
61             LOG.debug("Bundle {} does not have an entry for {}", bundle, YANG_MODLE_BINDING_PROVIDER_SERVICE);
62             return ImmutableList.of();
63         }
64
65         LOG.debug("Got addingBundle({}) with YangModelBindingProvider resource {}", bundle, resource);
66         final List<String> lines;
67         try {
68             lines = Resources.readLines(resource, StandardCharsets.UTF_8);
69         } catch (IOException e) {
70             LOG.error("Error while reading {} from bundle {}", resource, bundle, e);
71             return ImmutableList.of();
72         }
73
74         if (lines.isEmpty()) {
75             LOG.debug("Bundle {} has empty services for {}", bundle, YANG_MODLE_BINDING_PROVIDER_SERVICE);
76             return ImmutableList.of();
77         }
78
79         final List<YangModuleInfo> infos = new ArrayList<>(lines.size());
80         for (String moduleInfoName : lines) {
81             LOG.trace("Retrieve ModuleInfo({}, {})", moduleInfoName, bundle);
82             final YangModuleInfo moduleInfo;
83             try {
84                 moduleInfo = retrieveModuleInfo(moduleInfoName, bundle);
85             } catch (ScanningException e) {
86                 LOG.warn("Failed to acquire {} from bundle {}, ignoring it", moduleInfoName, bundle, e);
87                 continue;
88             }
89
90             infos.add(moduleInfo);
91         }
92
93         final List<ObjectRegistration<YangModuleInfo>> registrations = moduleInfoRegistry.registerInfos(infos);
94         LOG.trace("Bundle {} resulted in registrations {}", bundle, registrations);
95         moduleInfoRegistry.scannerUpdate();
96         return registrations;
97     }
98
99     @Override
100     public void modifiedBundle(final Bundle bundle, final BundleEvent event,
101             final List<ObjectRegistration<YangModuleInfo>> regs) {
102         if (bundle.getBundleId() == Constants.SYSTEM_BUNDLE_ID) {
103             LOG.debug("Framework bundle {} got event {}", bundle, event.getType());
104             if ((event.getType() & BundleEvent.STOPPING) != 0) {
105                 LOG.info("OSGi framework is being stopped, halting bundle scanning");
106                 moduleInfoRegistry.scannerShutdown();
107             }
108         }
109     }
110
111
112     @Override
113     public void removedBundle(final Bundle bundle, final BundleEvent event,
114             final List<ObjectRegistration<YangModuleInfo>> regs) {
115         regs.forEach(ObjectRegistration::close);
116         moduleInfoRegistry.scannerUpdate();
117     }
118
119     private static YangModuleInfo retrieveModuleInfo(final String className, final Bundle bundle)
120             throws ScanningException {
121         final Class<?> loadedClass;
122         try {
123             loadedClass = bundle.loadClass(className);
124         } catch (ClassNotFoundException e) {
125             throw new ScanningException(e, "Failed to load class %s", className);
126         }
127
128         final Class<? extends YangModelBindingProvider> providerClass;
129         try {
130             providerClass = loadedClass.asSubclass(YangModelBindingProvider.class);
131         } catch (ClassCastException e) {
132             throw new ScanningException(e, "Failed to validate %s", loadedClass);
133         }
134
135         final Constructor<? extends YangModelBindingProvider> ctor;
136         try {
137             ctor = providerClass.getDeclaredConstructor();
138         } catch (NoSuchMethodException e) {
139             throw new ScanningException(e, "%s does not have a no-argument constructor", providerClass);
140         } catch (SecurityException e) {
141             throw new ScanningException(e, "Failed to reflect on %s", providerClass);
142         }
143
144         YangModelBindingProvider instance;
145         try {
146             instance = ctor.newInstance();
147         } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
148                 | InvocationTargetException e) {
149             throw new ScanningException(e, "Failed to instantiate %s", providerClass);
150         }
151
152         return instance.getModuleInfo();
153     }
154
155     @NonNullByDefault
156     private static final class ScanningException extends Exception {
157         private static final long serialVersionUID = 1L;
158
159         ScanningException(final Exception cause, final String format, final Object... args) {
160             super(String.format(format, args), cause);
161         }
162     }
163 }