Skip to content

Commit

Permalink
Merge pull request #23 from kentyou/issues-2-3-6-7-17
Browse files Browse the repository at this point in the history
Implementation of "160. Feature Launcher Service Specification" ( issues 2, 3, 6, 7, 17 )
  • Loading branch information
ideas-into-software authored Oct 4, 2024
2 parents 761fbba + 0c1ac89 commit 3a87bb3
Show file tree
Hide file tree
Showing 22 changed files with 1,566 additions and 158 deletions.
59 changes: 52 additions & 7 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>17</maven.compiler.release>
<m2RepoPath>${project.build.directory}/m2Repo</m2RepoPath>
<localRepositoryPath>${project.build.directory}/m2Repo</localRepositoryPath>
</properties>

<dependencyManagement>
Expand Down Expand Up @@ -68,6 +68,17 @@
<version>2.0.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.configadmin</artifactId>
<version>1.9.26</version>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.util.converter</artifactId>
<version>1.0.9</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.cm.json</artifactId>
Expand All @@ -89,6 +100,21 @@
<artifactId>slf4j-simple</artifactId>
<version>2.0.9</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-resolver-provider</artifactId>
<version>4.0.0-beta-4</version>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-supplier-mvn4</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-spi</artifactId>
<version>2.0.1</version>
</dependency>

<!-- Testing -->
<dependency>
Expand Down Expand Up @@ -124,7 +150,7 @@
<scope>test</scope>
</dependency>

<!-- ensure "gogo console feature" bundles are present in local repo -->
<!-- ensure feature bundles are present in local repo -->
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.gogo.command</artifactId>
Expand All @@ -143,6 +169,24 @@
<version>1.1.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>biz.aQute</groupId>
<artifactId>biz.aQute.gogo.commands.provider</artifactId>
<version>1.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>biz.aQute.bnd</groupId>
<artifactId>biz.aQute.bnd.util</artifactId>
<version>7.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.aries.spifly</groupId>
<artifactId>org.apache.aries.spifly.dynamic.framework.extension</artifactId>
<version>1.3.6</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -207,7 +251,7 @@
<bnd><![CDATA[
Provide-Capability: \
osgi.service;objectClass:List<String>="org.osgi.service.featurelauncher.runtime.FeatureRuntime";uses:="org.osgi.service.featurelauncher.runtime",\
osgi.service;objectClass:List<String>="org.osgi.service.featurelauncher.repository.ArtifactRepositoryFactory";uses:="org.osgi.service.featurelauncher.repository"
osgi.service;objectClass:List<String>="org.osgi.service.featurelauncher.repository.ArtifactRepositoryFactory";uses:="org.osgi.service.featurelauncher.repository"
]]></bnd>
</configuration>
</execution>
Expand Down Expand Up @@ -235,7 +279,7 @@
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${m2RepoPath}</outputDirectory>
<outputDirectory>${localRepositoryPath}</outputDirectory>
<useRepositoryLayout>true</useRepositoryLayout>
<copyPom>true</copyPom>
</configuration>
Expand All @@ -254,8 +298,8 @@
<configuration>
<properties>
<property>
<name>M2_REPO_PATH</name>
<value>${m2RepoPath}</value>
<name>localRepositoryPath</name>
<value>${localRepositoryPath}</value>
</property>
</properties>
</configuration>
Expand All @@ -266,7 +310,8 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- needed to make system properties set via 'properties-maven-plugin' available in Java code -->
<!-- needed to make system properties set via
'properties-maven-plugin' available in Java code -->
<forkCount>0</forkCount>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
/**
* Copyright (c) 2024 Kentyou and others.
* All rights reserved.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Kentyou - initial implementation
*/
package com.kentyou.featurelauncher.impl;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Dictionary;
import java.util.List;
import java.util.Map;

import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.feature.FeatureConfiguration;
import org.osgi.service.featurelauncher.LaunchException;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Manages feature configurations via Configuration Admin Service.
*
* As defined in the following sections of the "160. Feature Launcher Service Specification":
* - 160.4.3.4
* - 160.4.3.5
* - 160.5.2.1.3
*
* @author Michael H. Siemaszko (mhs@into.software)
* @since Sep 25, 2024
*/
class FeatureConfigurationManager implements ServiceTrackerCustomizer<ConfigurationAdmin, Object> {
private static final Logger LOG = LoggerFactory.getLogger(FeatureConfigurationManager.class);

private static final String CONFIGURATION_ADMIN_CLASS_NAME = "org.osgi.service.cm.ConfigurationAdmin";
private static final String CONFIGURATION_CLASS_NAME = "org.osgi.service.cm.Configuration";
public static final long CONFIGURATION_TIMEOUT_DEFAULT = 5000;

private final BundleContext bundleContext;
private final Map<String, FeatureConfiguration> featureConfigurations;

private final ServiceTracker<ConfigurationAdmin, Object> serviceTracker;

private Class<?> configurationAdminClass;
private Class<?> configurationClass;

private Method listConfigurationsMethod;
private Method getFactoryConfigurationMethod;
private Method getConfigurationMethod;
private Method getConfigurationPropertiesMethod;
private Method updateConfigurationPropertiesMethod;

public FeatureConfigurationManager(BundleContext bundleContext,
Map<String, FeatureConfiguration> featureConfigurations) {
this.bundleContext = bundleContext;
this.featureConfigurations = featureConfigurations;

this.serviceTracker = new ServiceTracker<>(this.bundleContext, ConfigurationAdmin.class, this);
this.serviceTracker.open(true);
}

// TODO: handle other timeout values as defined in 160.8.4.3
public void waitForService(long timeout) {
try {
if (serviceTracker.waitForService(timeout) == null) {
throw new LaunchException("'ConfigurationAdmin' service is not available!");
}
} catch (InterruptedException e) {
throw new LaunchException("Error awaiting 'ConfigurationAdmin' service!", e);
}
}

public void stop() {
serviceTracker.close();
}

/*
* (non-Javadoc)
* @see org.osgi.util.tracker.ServiceTrackerCustomizer#addingService(org.osgi.framework.ServiceReference)
*/
@Override
public Object addingService(ServiceReference<ConfigurationAdmin> reference) {
LOG.info("Added ConfigurationAdmin service"); // TODO: change to debug level

createConfigurationsIfNeeded(reference);

return bundleContext.getService(reference);
}

/*
* (non-Javadoc)
* @see org.osgi.util.tracker.ServiceTrackerCustomizer#modifiedService(org.osgi.framework.ServiceReference, java.lang.Object)
*/
@Override
public void modifiedService(ServiceReference<ConfigurationAdmin> reference, Object service) {
// NOP
}

/*
* (non-Javadoc)
* @see org.osgi.util.tracker.ServiceTrackerCustomizer#removedService(org.osgi.framework.ServiceReference, java.lang.Object)
*/
@Override
public void removedService(ServiceReference<ConfigurationAdmin> reference, Object service) {
bundleContext.ungetService(reference);

LOG.info("Removed ConfigurationAdmin service"); // TODO: change to debug level
}

private void createConfigurationsIfNeeded(ServiceReference<ConfigurationAdmin> reference) {
if (!featureConfigurations.isEmpty()) {
LOG.info(String.format("There are %d feature configuration(s) to create", featureConfigurations.size()));

try {
Object configurationAdminService = bundleContext.getService(reference);

this.configurationAdminClass = reference.getBundle().loadClass(CONFIGURATION_ADMIN_CLASS_NAME);
this.configurationClass = reference.getBundle().loadClass(CONFIGURATION_CLASS_NAME);

this.listConfigurationsMethod = configurationAdminClass.getMethod("listConfigurations", String.class);
this.getFactoryConfigurationMethod = configurationAdminClass.getMethod("getFactoryConfiguration",
String.class, String.class, String.class);
this.getConfigurationMethod = configurationAdminClass.getMethod("getConfiguration", String.class,
String.class);
this.getConfigurationPropertiesMethod = configurationClass.getMethod("getProperties");
this.updateConfigurationPropertiesMethod = configurationClass.getMethod("update", Dictionary.class);

featureConfigurations.forEach((featureConfigurationPid, featureConfiguration) -> createConfiguration(
featureConfigurationPid, featureConfiguration, configurationAdminService));

} catch (ClassNotFoundException | NoSuchMethodException | SecurityException e) {
LOG.error("Error creating configurations!", e);
}

} else {
LOG.info("Feature has no configurations!");
}
}

private void createConfiguration(String featureConfigurationPid, FeatureConfiguration featureConfiguration,
Object configurationAdminService) {
if (featureConfiguration.getFactoryPid().isPresent()) {
createFactoryConfiguration(featureConfigurationPid, featureConfiguration, configurationAdminService);
return;
}

try {
LOG.info(String.format("Creating configuration %s", featureConfigurationPid));

Object configurationObject = getConfigurationMethod.invoke(configurationAdminService,
featureConfiguration.getPid(), "?");

updateConfigurationProperties(configurationObject, featureConfigurationPid, featureConfiguration);

} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
LOG.error(String.format("Error creating configuration %s!", featureConfigurationPid), e);
}
}

private void createFactoryConfiguration(String featureConfigurationPid, FeatureConfiguration featureConfiguration,
Object configurationAdminService) {
try {
LOG.info(String.format("Creating factory configuration %s", featureConfigurationPid));

Object configurationObject = getFactoryConfigurationMethod.invoke(configurationAdminService,
featureConfiguration.getFactoryPid().get(), featureConfiguration.getPid(), "?");

updateConfigurationProperties(configurationObject, featureConfigurationPid, featureConfiguration);

} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
LOG.error(String.format("Error creating configuration %s!", featureConfigurationPid), e);
}
}

private void updateConfigurationProperties(Object configurationObject, String featureConfigurationPid,
FeatureConfiguration featureConfiguration) {
Dictionary<String, Object> configurationProperties = FrameworkUtil
.asDictionary(featureConfiguration.getValues());

try {
updateConfigurationPropertiesMethod.invoke(configurationObject, configurationProperties);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
LOG.error(String.format("Error updating configuration properties %s!", featureConfigurationPid), e);
}
}

@SuppressWarnings("unused")
private List<Map<String, Object>> listConfigurations(Object configurationAdminService, String filter) {
try {
Object result = listConfigurationsMethod.invoke(configurationAdminService, filter);
if (result != null) {
List<Map<String, Object>> configurations = new ArrayList<>();
for (Object configObj : (Object[]) result) {
configurations.add(getConfigurationProperties(configObj));
}
return configurations;
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
LOG.error("Error listing configurations!", e);
}

return Collections.emptyList();
}

@SuppressWarnings("unchecked")
private Map<String, Object> getConfigurationProperties(Object configurationObject)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
return FrameworkUtil
.asMap((Dictionary<String, Object>) getConfigurationPropertiesMethod.invoke(configurationObject));
}
}
Loading

0 comments on commit 3a87bb3

Please sign in to comment.