2 * Copyright (c) 2017, 2020 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.dom.schema.osgi.impl;
10 import static java.util.Objects.requireNonNull;
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;
35 * Tracks bundles and attempts to retrieve YangModuleInfo, which is then fed into ModuleInfoRegistry.
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/";
44 private final YangModuleInfoRegistry moduleInfoRegistry;
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);
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;
59 // Load YangModuleInfos
60 final var moduleInfos = loadBundleServices(bundle, YangModelBindingProvider.class).stream()
61 .map(YangModelBindingProvider::getModuleInfo)
62 .collect(Collectors.toUnmodifiableList());
64 // Load YangFeatureProviders
65 @SuppressWarnings({ "rawtypes", "unchecked" })
66 final List<YangFeatureProvider<?>> featureProviders =
67 (List) loadBundleServices(bundle, YangFeatureProvider.class);
69 if (moduleInfos.isEmpty() && featureProviders.isEmpty()) {
70 LOG.debug("Bundle {} does not have any interesting service", bundle);
74 final var reg = moduleInfoRegistry.registerBundle(moduleInfos, featureProviders);
75 moduleInfoRegistry.scannerUpdate();
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();
91 public void removedBundle(final Bundle bundle, final BundleEvent event, final Registration object) {
93 moduleInfoRegistry.scannerUpdate();
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);
105 LOG.debug("Got addingBundle({}) with {} resource {}", bundle, serviceName, resource);
106 final List<String> lines;
108 lines = Resources.readLines(resource, StandardCharsets.UTF_8);
109 } catch (IOException e) {
110 LOG.error("Error while reading {} from bundle {}", resource, bundle, e);
114 if (lines.isEmpty()) {
115 LOG.debug("Bundle {} has empty services for {}", bundle, serviceEntry);
119 final var services = new ArrayList<T>(lines.size());
120 for (var implName : lines) {
121 LOG.trace("Retrieve ModuleInfo({}, {})", implName, bundle);
124 service = loadImpl(serviceClass, bundle, implName);
125 } catch (ScanningException e) {
126 LOG.warn("Failed to acquire {} from bundle {}, ignoring it", implName, bundle, e);
130 services.add(service);
135 private static <T> @NonNull T loadImpl(final Class<T> type, final Bundle bundle, final String className)
136 throws ScanningException {
137 final Class<?> loadedClass;
139 loadedClass = bundle.loadClass(className);
140 } catch (ClassNotFoundException e) {
141 throw new ScanningException(e, "Failed to load class %s", className);
144 final Class<? extends T> providerClass;
146 providerClass = loadedClass.asSubclass(type);
147 } catch (ClassCastException e) {
148 throw new ScanningException(e, "Failed to validate %s", loadedClass);
151 final Constructor<? extends T> ctor;
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);
161 return ctor.newInstance();
162 } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
163 | InvocationTargetException e) {
164 throw new ScanningException(e, "Failed to instantiate %s", providerClass);
169 private static final class ScanningException extends Exception {
171 private static final long serialVersionUID = 1L;
173 ScanningException(final Exception cause, final String format, final Object... args) {
174 super(String.format(format, args), cause);