Pick up byte-buddy from yangtools
[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.io.Resources;
13 import java.io.IOException;
14 import java.io.Serial;
15 import java.lang.reflect.Constructor;
16 import java.lang.reflect.InvocationTargetException;
17 import java.nio.charset.StandardCharsets;
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.stream.Collectors;
21 import org.eclipse.jdt.annotation.NonNull;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.opendaylight.yangtools.concepts.Registration;
24 import org.opendaylight.yangtools.yang.binding.YangFeatureProvider;
25 import org.opendaylight.yangtools.yang.binding.YangModelBindingProvider;
26 import org.osgi.framework.Bundle;
27 import org.osgi.framework.BundleContext;
28 import org.osgi.framework.BundleEvent;
29 import org.osgi.framework.Constants;
30 import org.osgi.util.tracker.BundleTracker;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 /**
35  * Tracks bundles and attempts to retrieve YangModuleInfo, which is then fed into ModuleInfoRegistry.
36  */
37 final class YangModuleInfoScanner extends BundleTracker<Registration> {
38     private static final Logger LOG = LoggerFactory.getLogger(YangModuleInfoScanner.class);
39     // Special registration for when we want to track the bundle, but there is nothing in there
40     private static final Registration NOOP_REGISTRATION = () -> { };
41     // FIXME: this should be in a place shared with maven-sal-api-gen-plugin
42     private static final String MODULE_INFO_PROVIDER_PATH_PREFIX = "META-INF/services/";
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     public Registration addingBundle(final Bundle bundle, final BundleEvent event) {
53         if (bundle.getBundleId() == Constants.SYSTEM_BUNDLE_ID) {
54             // We do want to track this bundle so we get modifiedBundle() callback
55             LOG.debug("Not scanning system bundle {}", bundle);
56             return NOOP_REGISTRATION;
57         }
58
59         // Load YangModuleInfos
60         final var moduleInfos = loadBundleServices(bundle, YangModelBindingProvider.class).stream()
61             .map(YangModelBindingProvider::getModuleInfo)
62             .collect(Collectors.toUnmodifiableList());
63
64         // Load YangFeatureProviders
65         @SuppressWarnings({ "rawtypes", "unchecked" })
66         final List<YangFeatureProvider<?>> featureProviders =
67             (List) loadBundleServices(bundle, YangFeatureProvider.class);
68
69         if (moduleInfos.isEmpty() && featureProviders.isEmpty()) {
70             LOG.debug("Bundle {} does not have any interesting service", bundle);
71             return null;
72         }
73
74         final var reg = moduleInfoRegistry.registerBundle(moduleInfos, featureProviders);
75         moduleInfoRegistry.scannerUpdate();
76         return reg;
77     }
78
79     @Override
80     public void modifiedBundle(final Bundle bundle, final BundleEvent event, final Registration object) {
81         if (bundle.getBundleId() == Constants.SYSTEM_BUNDLE_ID) {
82             LOG.debug("Framework bundle {} got event {}", bundle, event.getType());
83             if ((event.getType() & BundleEvent.STOPPING) != 0) {
84                 LOG.info("OSGi framework is being stopped, halting bundle scanning");
85                 moduleInfoRegistry.scannerShutdown();
86             }
87         }
88     }
89
90     @Override
91     public void removedBundle(final Bundle bundle, final BundleEvent event, final Registration object) {
92         object.close();
93         moduleInfoRegistry.scannerUpdate();
94     }
95
96     private static <T> List<T> loadBundleServices(final Bundle bundle, final Class<T> serviceClass) {
97         final var serviceName = serviceClass.getName();
98         final var serviceEntry = MODULE_INFO_PROVIDER_PATH_PREFIX + serviceName;
99         final var resource = bundle.getEntry(serviceEntry);
100         if (resource == null) {
101             LOG.debug("Bundle {} does not have an entry for {}", bundle, serviceEntry);
102             return List.of();
103         }
104
105         LOG.debug("Got addingBundle({}) with {} resource {}", bundle, serviceName, resource);
106         final List<String> lines;
107         try {
108             lines = Resources.readLines(resource, StandardCharsets.UTF_8);
109         } catch (IOException e) {
110             LOG.error("Error while reading {} from bundle {}", resource, bundle, e);
111             return List.of();
112         }
113
114         if (lines.isEmpty()) {
115             LOG.debug("Bundle {} has empty services for {}", bundle, serviceEntry);
116             return List.of();
117         }
118
119         final var services = new ArrayList<T>(lines.size());
120         for (var implName : lines) {
121             LOG.trace("Retrieve ModuleInfo({}, {})", implName, bundle);
122             final T service;
123             try {
124                 service = loadImpl(serviceClass, bundle, implName);
125             } catch (ScanningException e) {
126                 LOG.warn("Failed to acquire {} from bundle {}, ignoring it", implName, bundle, e);
127                 continue;
128             }
129
130             services.add(service);
131         }
132         return services;
133     }
134
135     private static <T> @NonNull T loadImpl(final Class<T> type, final Bundle bundle, final String className)
136             throws ScanningException {
137         final Class<?> loadedClass;
138         try {
139             loadedClass = bundle.loadClass(className);
140         } catch (ClassNotFoundException e) {
141             throw new ScanningException(e, "Failed to load class %s", className);
142         }
143
144         final Class<? extends T> providerClass;
145         try {
146             providerClass = loadedClass.asSubclass(type);
147         } catch (ClassCastException e) {
148             throw new ScanningException(e, "Failed to validate %s", loadedClass);
149         }
150
151         final Constructor<? extends T> ctor;
152         try {
153             ctor = providerClass.getDeclaredConstructor();
154         } catch (NoSuchMethodException e) {
155             throw new ScanningException(e, "%s does not have a no-argument constructor", providerClass);
156         } catch (SecurityException e) {
157             throw new ScanningException(e, "Failed to reflect on %s", providerClass);
158         }
159
160         try {
161             return ctor.newInstance();
162         } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
163                 | InvocationTargetException e) {
164             throw new ScanningException(e, "Failed to instantiate %s", providerClass);
165         }
166     }
167
168     @NonNullByDefault
169     private static final class ScanningException extends Exception {
170         @Serial
171         private static final long serialVersionUID = 1L;
172
173         ScanningException(final Exception cause, final String format, final Object... args) {
174             super(String.format(format, args), cause);
175         }
176     }
177 }