From fd508c77302c18226efbfd2b683d83f71f8f7ad1 Mon Sep 17 00:00:00 2001 From: "Michael H. Siemaszko" Date: Thu, 19 Sep 2024 23:28:48 +0200 Subject: [PATCH 1/2] Implementation of "160. Feature Launcher Service Specification" - Implementation of 'ArtifactRepository' for local repository type - Launching default framework instance based on the supplied feature with local artifact repository - Parse JSON representation of Feature definition - Locate default OSGi framework implementation - Create OSGi Framework instance - Install bundles in OSGi framework instance - Start OSGi framework instance - Cleanup after failure - Throw LaunchException if an error occurs creating the framework, or locating and installing any of the feature bundles - Register Artifact Repository Factory and any necessary module metadata with the Java ServiceLoader API - Register Feature Launcher Service and any necessary module metadata with the Java ServiceLoader API - Project skeleton - Use Maven for build automation Signed-off-by: Michael H. Siemaszko --- .classpath | 40 ++ .gitignore | 1 + .project | 23 + .settings/org.eclipse.core.resources.prefs | 6 + .settings/org.eclipse.jdt.core.prefs | 8 + .settings/org.eclipse.m2e.core.prefs | 4 + pom.xml | 227 ++++++++++ .../impl/FeatureLauncherImpl.java | 403 ++++++++++++++++++ .../DecoratorBuilderFactoryImpl.java | 83 ++++ .../impl/decorator/FeatureDecoratorImpl.java | 40 ++ .../FeatureExtensionHandlerImpl.java | 42 ++ .../ArtifactRepositoryFactoryImpl.java | 65 +++ .../LocalArtifactRepositoryImpl.java | 114 +++++ .../RemoteArtifactRepositoryImpl.java | 39 ++ .../impl/runtime/FeatureRuntimeImpl.java | 126 ++++++ .../impl/runtime/InstalledBundleImpl.java | 81 ++++ .../runtime/InstalledConfigurationImpl.java | 71 +++ .../impl/runtime/InstalledFeatureImpl.java | 71 +++ .../impl/runtime/RuntimeBundleMergeImpl.java | 45 ++ .../RuntimeConfigurationMergeImpl.java | 45 ++ .../impl/util/BundleEventUtil.java | 61 +++ .../impl/util/BundleStateUtil.java | 60 +++ .../impl/util/FrameworkEventUtil.java | 61 +++ .../impl/util/FrameworkFactoryLocator.java | 78 ++++ ...gi.service.featurelauncher.FeatureLauncher | 1 + ...ncher.repository.ArtifactRepositoryFactory | 1 + .../impl/FeatureLauncherImplTest.java | 92 ++++ .../LocalArtifactRepositoryImplTest.java | 123 ++++++ .../features/gogo-console-feature.json | 15 + 29 files changed, 2026 insertions(+) create mode 100644 .classpath create mode 100644 .project create mode 100644 .settings/org.eclipse.core.resources.prefs create mode 100644 .settings/org.eclipse.jdt.core.prefs create mode 100644 .settings/org.eclipse.m2e.core.prefs create mode 100644 pom.xml create mode 100644 src/main/java/com/kentyou/featurelauncher/impl/FeatureLauncherImpl.java create mode 100644 src/main/java/com/kentyou/featurelauncher/impl/decorator/DecoratorBuilderFactoryImpl.java create mode 100644 src/main/java/com/kentyou/featurelauncher/impl/decorator/FeatureDecoratorImpl.java create mode 100644 src/main/java/com/kentyou/featurelauncher/impl/decorator/FeatureExtensionHandlerImpl.java create mode 100644 src/main/java/com/kentyou/featurelauncher/impl/repository/ArtifactRepositoryFactoryImpl.java create mode 100644 src/main/java/com/kentyou/featurelauncher/impl/repository/LocalArtifactRepositoryImpl.java create mode 100644 src/main/java/com/kentyou/featurelauncher/impl/repository/RemoteArtifactRepositoryImpl.java create mode 100644 src/main/java/com/kentyou/featurelauncher/impl/runtime/FeatureRuntimeImpl.java create mode 100644 src/main/java/com/kentyou/featurelauncher/impl/runtime/InstalledBundleImpl.java create mode 100644 src/main/java/com/kentyou/featurelauncher/impl/runtime/InstalledConfigurationImpl.java create mode 100644 src/main/java/com/kentyou/featurelauncher/impl/runtime/InstalledFeatureImpl.java create mode 100644 src/main/java/com/kentyou/featurelauncher/impl/runtime/RuntimeBundleMergeImpl.java create mode 100644 src/main/java/com/kentyou/featurelauncher/impl/runtime/RuntimeConfigurationMergeImpl.java create mode 100644 src/main/java/com/kentyou/featurelauncher/impl/util/BundleEventUtil.java create mode 100644 src/main/java/com/kentyou/featurelauncher/impl/util/BundleStateUtil.java create mode 100644 src/main/java/com/kentyou/featurelauncher/impl/util/FrameworkEventUtil.java create mode 100644 src/main/java/com/kentyou/featurelauncher/impl/util/FrameworkFactoryLocator.java create mode 100644 src/main/resources/META-INF/services/org.osgi.service.featurelauncher.FeatureLauncher create mode 100644 src/main/resources/META-INF/services/org.osgi.service.featurelauncher.repository.ArtifactRepositoryFactory create mode 100644 src/test/java/com/kentyou/featurelauncher/impl/FeatureLauncherImplTest.java create mode 100644 src/test/java/com/kentyou/featurelauncher/impl/repository/LocalArtifactRepositoryImplTest.java create mode 100644 src/test/resources/features/gogo-console-feature.json diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..7a9a105 --- /dev/null +++ b/.classpath @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore index 524f096..a68cd72 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* replay_pid* +/target/ diff --git a/.project b/.project new file mode 100644 index 0000000..0d0304b --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + featurelauncher + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..29abf99 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..eeac0e7 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b2fa46c --- /dev/null +++ b/pom.xml @@ -0,0 +1,227 @@ + + + 4.0.0 + + com.kentyou + featurelauncher + 1.0.0-SNAPSHOT + jar + + Implementation of OSGi "160. Feature Launcher Service Specification" + https://github.com/kentyou/feature-launcher-prototype + + + + Michael H. Siemaszko + mhs@into.software + + + + + UTF-8 + 17 + + + + + + org.junit + junit-bom + 5.11.0 + pom + import + + + + + + + org.osgi + osgi.annotation + 8.0.0 + provided + + + org.osgi + osgi.core + 8.0.0 + provided + + + org.osgi + org.osgi.service.feature + 1.0.0 + provided + + + org.osgi + org.osgi.service.featurelauncher + 1.0.0-SNAPSHOT + provided + + + jakarta.json + jakarta.json-api + 2.0.2 + provided + + + org.apache.felix + org.apache.felix.cm.json + 2.0.0 + provided + + + org.apache.felix + org.apache.felix.feature + 1.0.2 + + + org.slf4j + slf4j-api + 2.0.9 + + + org.slf4j + slf4j-simple + 2.0.9 + + + + + junit + junit + 4.13.2 + test + + + org.mockito + mockito-core + 2.8.9 + test + + + org.apache.johnzon + johnzon-core + 1.2.19 + jakarta + test + + + org.junit.jupiter + junit-jupiter-api + test + + + + + org.apache.felix + org.apache.felix.framework + 7.0.5 + test + + + + + org.apache.felix + org.apache.felix.gogo.command + 1.1.2 + test + + + org.apache.felix + org.apache.felix.gogo.shell + 1.1.4 + test + + + org.apache.felix + org.apache.felix.gogo.runtime + 1.1.6 + test + + + + + + + + + maven-clean-plugin + 3.4.0 + + + + maven-resources-plugin + 3.3.1 + + + maven-compiler-plugin + 3.13.0 + + + maven-surefire-plugin + 3.3.0 + + + maven-jar-plugin + 3.4.2 + + + maven-install-plugin + 3.1.2 + + + maven-deploy-plugin + 3.1.2 + + + + maven-site-plugin + 3.12.1 + + + maven-project-info-reports-plugin + 3.6.1 + + + + + + biz.aQute.bnd + bnd-maven-plugin + 7.0.0 + + + + bnd-process + + + ="org.osgi.service.featurelauncher.runtime.FeatureRuntime";uses:="org.osgi.service.featurelauncher.runtime",\ + osgi.service;objectClass:List="org.osgi.service.featurelauncher.repository.ArtifactRepositoryFactory";uses:="org.osgi.service.featurelauncher.repository" + ]]> + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + diff --git a/src/main/java/com/kentyou/featurelauncher/impl/FeatureLauncherImpl.java b/src/main/java/com/kentyou/featurelauncher/impl/FeatureLauncherImpl.java new file mode 100644 index 0000000..c74c067 --- /dev/null +++ b/src/main/java/com/kentyou/featurelauncher/impl/FeatureLauncherImpl.java @@ -0,0 +1,403 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.ServiceLoader; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.BundleException; +import org.osgi.framework.Constants; +import org.osgi.framework.FrameworkEvent; +import org.osgi.framework.launch.Framework; +import org.osgi.framework.launch.FrameworkFactory; +import org.osgi.service.feature.Feature; +import org.osgi.service.feature.FeatureBundle; +import org.osgi.service.feature.FeatureService; +import org.osgi.service.feature.ID; +import org.osgi.service.featurelauncher.FeatureLauncher; +import org.osgi.service.featurelauncher.LaunchException; +import org.osgi.service.featurelauncher.decorator.FeatureDecorator; +import org.osgi.service.featurelauncher.decorator.FeatureExtensionHandler; +import org.osgi.service.featurelauncher.repository.ArtifactRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.kentyou.featurelauncher.impl.repository.ArtifactRepositoryFactoryImpl; +import com.kentyou.featurelauncher.impl.util.BundleEventUtil; +import com.kentyou.featurelauncher.impl.util.FrameworkEventUtil; +import com.kentyou.featurelauncher.impl.util.FrameworkFactoryLocator; + +/** + * 160.4 The Feature Launcher + * + * @author Michael H. Siemaszko (mhs@into.software) + * @since Sep 15, 2024 + */ +public class FeatureLauncherImpl extends ArtifactRepositoryFactoryImpl implements FeatureLauncher { + private static final Logger LOG = LoggerFactory.getLogger(FeatureLauncherImpl.class); + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.FeatureLauncher#launch(org.osgi.service.feature.Feature) + */ + @Override + public LaunchBuilder launch(Feature feature) { + Objects.requireNonNull(feature, "Feature cannot be null!"); + + return new LaunchBuilderImpl(feature); + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.FeatureLauncher#launch(java.io.Reader) + */ + @Override + public LaunchBuilder launch(Reader jsonReader) { + Objects.requireNonNull(jsonReader, "Feature JSON cannot be null!"); + + FeatureService featureService = loadFeatureService(); + + try { + Feature feature = featureService.readFeature(jsonReader); + + return launch(feature); + + } catch (IOException e) { + LOG.error("Error reading feature!", e); + throw new LaunchException("Error reading feature!", e); + } + } + + class LaunchBuilderImpl implements LaunchBuilder { + private final Feature feature; + private boolean isLaunched; + private List installedBundles; + private ArtifactRepository artifactRepository; + private Map configuration; + private Map variables; + private Map frameworkProps; + private List decorators; + private Map extensionHandlers; + + LaunchBuilderImpl(Feature feature) { + Objects.requireNonNull(feature, "Feature cannot be null!"); + + this.feature = feature; + this.isLaunched = false; + this.installedBundles = new ArrayList<>(); + this.configuration = new HashMap<>(); + this.variables = new HashMap<>(); + this.frameworkProps = new HashMap<>(); + this.decorators = new ArrayList<>(); + this.extensionHandlers = new HashMap<>(); + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.FeatureLauncher.LaunchBuilder#withRepository(org.osgi.service.featurelauncher.repository.ArtifactRepository) + */ + @Override + public LaunchBuilder withRepository(ArtifactRepository repository) { + Objects.requireNonNull(repository, "Artifact Repository cannot be null!"); + + ensureNotLaunchedYet(); + + this.artifactRepository = repository; + + return this; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.FeatureLauncher.LaunchBuilder#withConfiguration(java.util.Map) + */ + @Override + public LaunchBuilder withConfiguration(Map configuration) { + Objects.requireNonNull(configuration, "Configuration cannot be null!"); + + ensureNotLaunchedYet(); + + this.configuration = configuration; + + return this; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.FeatureLauncher.LaunchBuilder#withVariables(java.util.Map) + */ + @Override + public LaunchBuilder withVariables(Map variables) { + Objects.requireNonNull(variables, "Variables cannot be null!"); + + ensureNotLaunchedYet(); + + this.variables = variables; + + return this; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.FeatureLauncher.LaunchBuilder#withFrameworkProperties(java.util.Map) + */ + @Override + public LaunchBuilder withFrameworkProperties(Map frameworkProps) { + Objects.requireNonNull(frameworkProps, "Framework launch properties cannot be null!"); + + ensureNotLaunchedYet(); + + this.frameworkProps = frameworkProps; + + return this; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.FeatureLauncher.LaunchBuilder#withDecorator(org.osgi.service.featurelauncher.decorator.FeatureDecorator) + */ + @Override + public LaunchBuilder withDecorator(FeatureDecorator decorator) { + Objects.requireNonNull(decorator, "Feature Decorator cannot be null!"); + + ensureNotLaunchedYet(); + + this.decorators.add(decorator); + + return this; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.FeatureLauncher.LaunchBuilder#withExtensionHandler(java.lang.String, org.osgi.service.featurelauncher.decorator.FeatureExtensionHandler) + */ + @Override + public LaunchBuilder withExtensionHandler(String extensionName, FeatureExtensionHandler extensionHandler) { + Objects.requireNonNull(extensionName, "Feature extension name cannot be null!"); + Objects.requireNonNull(extensionHandler, "Feature extension handler cannot be null!"); + + ensureNotLaunchedYet(); + + this.extensionHandlers.put(extensionName, extensionHandler); + + return this; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.FeatureLauncher.LaunchBuilder#launchFramework() + */ + @Override + public Framework launchFramework() { + Objects.requireNonNull(feature, "Feature is required!"); + Objects.requireNonNull(artifactRepository, "Artifact Repository is required!"); + + ensureNotLaunchedYet(); + + ////////////////////////////////////// + // TODO: 160.4.3.1: Feature Decoration + + ///////////////////////////////////////////////// + // 160.4.3.2: Locating a framework implementation + + // use default framework implementation for now + FrameworkFactory frameworkFactory = FrameworkFactoryLocator.loadDefaultFrameworkFactory(); + Objects.requireNonNull(frameworkFactory, "Framework Factory cannot be null!"); + + /////////////////////////////////////////// + // 160.4.3.3: Creating a Framework instance + + Framework framework = createFramework(frameworkFactory, Collections.emptyMap()); + + addLogListeners(framework); + + ///////////////////////////////////////////////////////// + // 160.4.3.4: Installing bundles and configurations + installBundles(framework); + + // TODO: install configurations + + ////////////////////////////////////////// + // 160.4.3.5: Starting the framework + startFramework(framework); + + this.isLaunched = true; + + return framework; + } + + private Framework createFramework(FrameworkFactory frameworkFactory, Map frameworkProperties) { + Framework framework = frameworkFactory.newFramework(frameworkProperties); + try { + framework.init(); + } catch (BundleException e) { + LOG.error("Could not initialize framework!", e); + throw new LaunchException("Could not initialize framework!", e); + } + return framework; + } + + private void startFramework(Framework framework) { + LOG.info("Starting framework.."); + try { + framework.start(); + + startBundles(); + + createShutdownHook(framework); + + } catch (BundleException e) { + //////////////////////////////////// + // 160.4.3.6: Cleanup after failure + cleanup(framework); + + LOG.error("Could not start framework!", e); + throw new LaunchException("Could not start framework!", e); + } + } + + private void startBundles() throws BundleException { + for (Bundle installedBundle : installedBundles) { + startBundle(installedBundle); + } + } + + private void startBundle(Bundle installedBundle) throws BundleException { + if (installedBundle.getHeaders().get(Constants.FRAGMENT_HOST) == null) { + installedBundle.start(); + } + } + + private void addLogListeners(Framework framework) { + framework.getBundleContext().addFrameworkListener(this::logFrameworkEvent); + framework.getBundleContext().addBundleListener(this::logBundleEvent); + } + + private void installBundles(Framework framework) { + if (this.feature.getBundles() != null && this.feature.getBundles().size() > 0) { + + LOG.info(String.format("There are %d bundle(s) to install", this.feature.getBundles().size())); + + for (FeatureBundle featureBundle : this.feature.getBundles()) { + installBundle(framework, featureBundle); + } + + } else { + LOG.error("There are no bundles to install!"); + } + } + + private void installBundle(Framework framework, FeatureBundle featureBundle) { + ID featureBundleID = featureBundle.getID(); + + try (InputStream featureBundleIs = artifactRepository.getArtifact(featureBundleID)) { + Bundle installedBundle = framework.getBundleContext().installBundle(featureBundleID.toString(), + featureBundleIs); + installedBundles.add(installedBundle); + + LOG.info(String.format("Installed bundle '%s'", installedBundle.getSymbolicName())); + + } catch (IOException | BundleException e) { + LOG.error(String.format("Could not install bundle '%s'!", featureBundleID.toString()), e); + throw new LaunchException(String.format("Could not install bundle '%s'!", featureBundleID.toString()), + e); + } + } + + private void logFrameworkEvent(FrameworkEvent frameworkEvent) { + if (frameworkEvent.getType() == FrameworkEvent.ERROR) { + LOG.error(String.format("Framework ERROR event %s", frameworkEvent.toString())); + } else { + LOG.info(String.format("Framework event type %s: %s", + FrameworkEventUtil.getFrameworkEventString(frameworkEvent.getType()), + frameworkEvent.toString())); + } + } + + private void logBundleEvent(BundleEvent bundleEvent) { + LOG.info(String.format("Bundle '%s' event type %s: %s", bundleEvent.getBundle().getSymbolicName(), + BundleEventUtil.getBundleEventString(bundleEvent.getType()), bundleEvent.toString())); + } + + private void cleanup(Framework framework) { + if (!installedBundles.isEmpty()) { + Iterator installedBundlesIt = installedBundles.iterator(); + while (installedBundlesIt.hasNext()) { + Bundle installedBundle = installedBundlesIt.next(); + + try { + installedBundle.uninstall(); + + installedBundlesIt.remove(); + + LOG.info(String.format("Uninstalled bundle '%s'", installedBundle.getSymbolicName())); + } catch (BundleException exc) { + LOG.error(String.format("Cannot uninstall bundle '%s'", installedBundle.getSymbolicName())); + } + } + } + } + + private void createShutdownHook(Framework framework) { + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + try { + if (framework != null) { + LOG.info("Stopping framework.."); + + cleanup(framework); + + framework.stop(); + framework.waitForStop(0); + } + } catch (Exception e) { + LOG.error("Error stopping framework!", e); + } + } + }); + } + + private void ensureNotLaunchedYet() { + if (this.isLaunched == true) { + LOG.error("Framework already launched!"); + throw new IllegalStateException("Framework already launched!"); + } + } + } + + private FeatureService loadFeatureService() { + ServiceLoader loader = ServiceLoader.load(FeatureService.class); + + Optional featureServiceOptional = loader.findFirst(); + if (featureServiceOptional.isPresent()) { + return featureServiceOptional.get(); + } else { + LOG.error("Error loading FeatureService!"); + throw new LaunchException("Error loading FeatureService!"); + } + } +} diff --git a/src/main/java/com/kentyou/featurelauncher/impl/decorator/DecoratorBuilderFactoryImpl.java b/src/main/java/com/kentyou/featurelauncher/impl/decorator/DecoratorBuilderFactoryImpl.java new file mode 100644 index 0000000..e96bf4f --- /dev/null +++ b/src/main/java/com/kentyou/featurelauncher/impl/decorator/DecoratorBuilderFactoryImpl.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl.decorator; + +import org.osgi.service.feature.FeatureArtifactBuilder; +import org.osgi.service.feature.FeatureBundleBuilder; +import org.osgi.service.feature.FeatureConfigurationBuilder; +import org.osgi.service.feature.FeatureExtension.Kind; +import org.osgi.service.feature.FeatureExtension.Type; +import org.osgi.service.feature.FeatureExtensionBuilder; +import org.osgi.service.feature.ID; +import org.osgi.service.featurelauncher.decorator.DecoratorBuilderFactory; + +/** + * TODO + * + * @author Michael H. Siemaszko (mhs@into.software) + * @since Sep 15, 2024 + */ +class DecoratorBuilderFactoryImpl implements DecoratorBuilderFactory { + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.decorator.DecoratorBuilderFactory#newArtifactBuilder(org.osgi.service.feature.ID) + */ + @Override + public FeatureArtifactBuilder newArtifactBuilder(ID id) { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.decorator.DecoratorBuilderFactory#newBundleBuilder(org.osgi.service.feature.ID) + */ + @Override + public FeatureBundleBuilder newBundleBuilder(ID id) { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.decorator.DecoratorBuilderFactory#newConfigurationBuilder(java.lang.String) + */ + @Override + public FeatureConfigurationBuilder newConfigurationBuilder(String pid) { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.decorator.DecoratorBuilderFactory#newConfigurationBuilder(java.lang.String, java.lang.String) + */ + @Override + public FeatureConfigurationBuilder newConfigurationBuilder(String factoryPid, String name) { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.decorator.DecoratorBuilderFactory#newExtensionBuilder(java.lang.String, org.osgi.service.feature.FeatureExtension.Type, org.osgi.service.feature.FeatureExtension.Kind) + */ + @Override + public FeatureExtensionBuilder newExtensionBuilder(String name, Type type, Kind kind) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/com/kentyou/featurelauncher/impl/decorator/FeatureDecoratorImpl.java b/src/main/java/com/kentyou/featurelauncher/impl/decorator/FeatureDecoratorImpl.java new file mode 100644 index 0000000..8f937c8 --- /dev/null +++ b/src/main/java/com/kentyou/featurelauncher/impl/decorator/FeatureDecoratorImpl.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl.decorator; + +import org.osgi.service.feature.Feature; +import org.osgi.service.featurelauncher.decorator.AbandonOperationException; +import org.osgi.service.featurelauncher.decorator.DecoratorBuilderFactory; +import org.osgi.service.featurelauncher.decorator.FeatureDecorator; + +/** + * TODO + * + * @author Michael H. Siemaszko (mhs@into.software) + * @since Sep 15, 2024 + */ +class FeatureDecoratorImpl implements FeatureDecorator { + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.decorator.FeatureDecorator#decorate(org.osgi.service.feature.Feature, org.osgi.service.featurelauncher.decorator.FeatureDecorator.FeatureDecoratorBuilder, org.osgi.service.featurelauncher.decorator.DecoratorBuilderFactory) + */ + @Override + public Feature decorate(Feature feature, FeatureDecoratorBuilder decoratedFeatureBuilder, + DecoratorBuilderFactory factory) throws AbandonOperationException { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/com/kentyou/featurelauncher/impl/decorator/FeatureExtensionHandlerImpl.java b/src/main/java/com/kentyou/featurelauncher/impl/decorator/FeatureExtensionHandlerImpl.java new file mode 100644 index 0000000..fb159ca --- /dev/null +++ b/src/main/java/com/kentyou/featurelauncher/impl/decorator/FeatureExtensionHandlerImpl.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl.decorator; + +import org.osgi.service.feature.Feature; +import org.osgi.service.feature.FeatureExtension; +import org.osgi.service.featurelauncher.decorator.AbandonOperationException; +import org.osgi.service.featurelauncher.decorator.DecoratorBuilderFactory; +import org.osgi.service.featurelauncher.decorator.FeatureExtensionHandler; + +/** + * TODO + * + * @author Michael H. Siemaszko (mhs@into.software) + * @since Sep 15, 2024 + */ +class FeatureExtensionHandlerImpl implements FeatureExtensionHandler { + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.decorator.FeatureExtensionHandler#handle(org.osgi.service.feature.Feature, org.osgi.service.feature.FeatureExtension, org.osgi.service.featurelauncher.decorator.FeatureExtensionHandler.FeatureExtensionHandlerBuilder, org.osgi.service.featurelauncher.decorator.DecoratorBuilderFactory) + */ + @Override + public Feature handle(Feature feature, FeatureExtension extension, + FeatureExtensionHandlerBuilder decoratedFeatureBuilder, DecoratorBuilderFactory factory) + throws AbandonOperationException { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/com/kentyou/featurelauncher/impl/repository/ArtifactRepositoryFactoryImpl.java b/src/main/java/com/kentyou/featurelauncher/impl/repository/ArtifactRepositoryFactoryImpl.java new file mode 100644 index 0000000..0b55a57 --- /dev/null +++ b/src/main/java/com/kentyou/featurelauncher/impl/repository/ArtifactRepositoryFactoryImpl.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl.repository; + +import java.net.URI; +import java.nio.file.Path; +import java.util.Map; +import java.util.Objects; + +import org.osgi.service.featurelauncher.repository.ArtifactRepository; +import org.osgi.service.featurelauncher.repository.ArtifactRepositoryFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 160.2.1 The Artifact Repository Factory + * + * @author Michael H. Siemaszko (mhs@into.software) + * @since Sep 15, 2024 + */ +public class ArtifactRepositoryFactoryImpl implements ArtifactRepositoryFactory { + private static final Logger LOG = LoggerFactory.getLogger(ArtifactRepositoryFactoryImpl.class); + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.repository.ArtifactRepositoryFactory#createRepository(java.nio.file.Path) + */ + @Override + public ArtifactRepository createRepository(Path path) { + Objects.requireNonNull(path, "Path cannot be null!"); + + if (!path.toFile().exists()) { + LOG.error(String.format("Path '%s' does not exist!", path.toString())); + throw new IllegalArgumentException(String.format("Path '%s' does not exist!", path.toString())); + } + + if (!path.toFile().isDirectory()) { + LOG.error(String.format("Path '%s' is not a directory!", path.toString())); + throw new IllegalArgumentException(String.format("Path '%s' is not a directory!", path.toString())); + } + + return new LocalArtifactRepositoryImpl(path); + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.repository.ArtifactRepositoryFactory#createRepository(java.net.URI, java.util.Map) + */ + @Override + public ArtifactRepository createRepository(URI uri, Map props) { + // TODO Auto-generated method stub + return null; + } +} diff --git a/src/main/java/com/kentyou/featurelauncher/impl/repository/LocalArtifactRepositoryImpl.java b/src/main/java/com/kentyou/featurelauncher/impl/repository/LocalArtifactRepositoryImpl.java new file mode 100644 index 0000000..fcae2f5 --- /dev/null +++ b/src/main/java/com/kentyou/featurelauncher/impl/repository/LocalArtifactRepositoryImpl.java @@ -0,0 +1,114 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl.repository; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import org.osgi.service.feature.ID; +import org.osgi.service.featurelauncher.repository.ArtifactRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 160.2.1.2 Local Repositories + * + * @author Michael H. Siemaszko (mhs@into.software) + * @since Sep 15, 2024 + */ +class LocalArtifactRepositoryImpl implements ArtifactRepository { + private static final Logger LOG = LoggerFactory.getLogger(LocalArtifactRepositoryImpl.class); + + private static final String DEFAULT_EXTENSION = "jar"; + + /** + * As per "Default + * Artifact Handlers Reference" + */ + // @formatter:off + private static final Map TYPE_TO_EXTENSION_MAP = Map.ofEntries( + Map.entry("pom", "pom"), + Map.entry("jar", DEFAULT_EXTENSION), + Map.entry("test-jar", DEFAULT_EXTENSION), + Map.entry("maven-plugin", DEFAULT_EXTENSION), + Map.entry("ejb", DEFAULT_EXTENSION), + Map.entry("ejb-client", DEFAULT_EXTENSION), + Map.entry("war", "war"), + Map.entry("ear", "ear"), + Map.entry("rar", "rar"), + Map.entry("java-source", DEFAULT_EXTENSION), + Map.entry("javadoc", DEFAULT_EXTENSION)); + // @formatter:on + + private final Path m2RepositoryPath; + + LocalArtifactRepositoryImpl(Path m2RepositoryPath) { + this.m2RepositoryPath = m2RepositoryPath; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.repository.ArtifactRepository#getArtifact(org.osgi.service.feature.ID) + */ + @Override + public InputStream getArtifact(ID id) { + Objects.requireNonNull(id, "ID cannot be null!"); + + Path path = getArtifactM2RepoPath(id); + + try { + return new FileInputStream(path.toFile()); + } catch (FileNotFoundException e) { + LOG.error(String.format("Error getting artifact ID '%s'", id.toString()), e); + + throw new RuntimeException(e); // TODO: clarify with Tim + } + } + + private Path getArtifactM2RepoPath(ID id) { + Path projectHome = Paths.get(m2RepositoryPath.toAbsolutePath().toString(), id.getGroupId().replace('.', '/')); + + StringBuilder artifactName = new StringBuilder(); + artifactName.append(id.getArtifactId()); + artifactName.append("-"); + artifactName.append(id.getVersion()); + if (id.getClassifier().isPresent()) { + artifactName.append("-"); + artifactName.append(id.getClassifier().get()); + } + artifactName.append("."); + artifactName.append(getExtensionForType(id.getType())); + + return Paths.get(projectHome.toAbsolutePath().toString(), id.getArtifactId(), id.getVersion(), + artifactName.toString()); + } + + private String getExtensionForType(Optional typeOptional) { + if (typeOptional.isPresent()) { + String type = typeOptional.get(); + if (TYPE_TO_EXTENSION_MAP.containsKey(type)) { + return TYPE_TO_EXTENSION_MAP.get(type); + } + } + + return DEFAULT_EXTENSION; + } +} diff --git a/src/main/java/com/kentyou/featurelauncher/impl/repository/RemoteArtifactRepositoryImpl.java b/src/main/java/com/kentyou/featurelauncher/impl/repository/RemoteArtifactRepositoryImpl.java new file mode 100644 index 0000000..a0610da --- /dev/null +++ b/src/main/java/com/kentyou/featurelauncher/impl/repository/RemoteArtifactRepositoryImpl.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl.repository; + +import java.io.InputStream; + +import org.osgi.service.feature.ID; +import org.osgi.service.featurelauncher.repository.ArtifactRepository; + +/** + * TODO + * + * @author Michael H. Siemaszko (mhs@into.software) + * @since Sep 15, 2024 + */ +class RemoteArtifactRepositoryImpl implements ArtifactRepository { + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.repository.ArtifactRepository#getArtifact(org.osgi.service.feature.ID) + */ + @Override + public InputStream getArtifact(ID id) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/com/kentyou/featurelauncher/impl/runtime/FeatureRuntimeImpl.java b/src/main/java/com/kentyou/featurelauncher/impl/runtime/FeatureRuntimeImpl.java new file mode 100644 index 0000000..b31ae9a --- /dev/null +++ b/src/main/java/com/kentyou/featurelauncher/impl/runtime/FeatureRuntimeImpl.java @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl.runtime; + +import java.io.Reader; +import java.net.URI; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +import org.osgi.service.feature.Feature; +import org.osgi.service.feature.ID; +import org.osgi.service.featurelauncher.repository.ArtifactRepository; +import org.osgi.service.featurelauncher.runtime.FeatureRuntime; +import org.osgi.service.featurelauncher.runtime.InstalledFeature; + +/** + * TODO + * + * @author Michael H. Siemaszko (mhs@into.software) + * @since Sep 15, 2024 + */ +class FeatureRuntimeImpl implements FeatureRuntime { + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.repository.ArtifactRepositoryFactory#createRepository(java.nio.file.Path) + */ + @Override + public ArtifactRepository createRepository(Path path) { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.repository.ArtifactRepositoryFactory#createRepository(java.net.URI, java.util.Map) + */ + @Override + public ArtifactRepository createRepository(URI uri, Map props) { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.FeatureRuntime#getDefaultRepositories() + */ + @Override + public Map getDefaultRepositories() { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.FeatureRuntime#install(org.osgi.service.feature.Feature) + */ + @Override + public InstallOperationBuilder install(Feature feature) { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.FeatureRuntime#install(java.io.Reader) + */ + @Override + public InstallOperationBuilder install(Reader jsonReader) { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.FeatureRuntime#getInstalledFeatures() + */ + @Override + public List getInstalledFeatures() { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.FeatureRuntime#remove(org.osgi.service.feature.ID) + */ + @Override + public void remove(ID featureId) { + // TODO Auto-generated method stub + + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.FeatureRuntime#update(org.osgi.service.feature.ID, org.osgi.service.feature.Feature) + */ + @Override + public UpdateOperationBuilder update(ID featureId, Feature feature) { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.FeatureRuntime#update(org.osgi.service.feature.ID, java.io.Reader) + */ + @Override + public UpdateOperationBuilder update(ID featureId, Reader jsonReader) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/com/kentyou/featurelauncher/impl/runtime/InstalledBundleImpl.java b/src/main/java/com/kentyou/featurelauncher/impl/runtime/InstalledBundleImpl.java new file mode 100644 index 0000000..0e320d7 --- /dev/null +++ b/src/main/java/com/kentyou/featurelauncher/impl/runtime/InstalledBundleImpl.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl.runtime; + +import java.util.Collection; +import java.util.List; + +import org.osgi.framework.Bundle; +import org.osgi.service.feature.ID; +import org.osgi.service.featurelauncher.runtime.InstalledBundle; + +/** + * TODO + * + * @author Michael H. Siemaszko (mhs@into.software) + * @since Sep 15, 2024 + */ +class InstalledBundleImpl implements InstalledBundle { + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.InstalledBundle#getBundleId() + */ + @Override + public ID getBundleId() { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.InstalledBundle#getAliases() + */ + @Override + public Collection getAliases() { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.InstalledBundle#getBundle() + */ + @Override + public Bundle getBundle() { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.InstalledBundle#getStartLevel() + */ + @Override + public int getStartLevel() { + // TODO Auto-generated method stub + return 0; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.InstalledBundle#getOwningFeatures() + */ + @Override + public List getOwningFeatures() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/com/kentyou/featurelauncher/impl/runtime/InstalledConfigurationImpl.java b/src/main/java/com/kentyou/featurelauncher/impl/runtime/InstalledConfigurationImpl.java new file mode 100644 index 0000000..ccdc3ba --- /dev/null +++ b/src/main/java/com/kentyou/featurelauncher/impl/runtime/InstalledConfigurationImpl.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl.runtime; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.osgi.service.feature.ID; +import org.osgi.service.featurelauncher.runtime.InstalledConfiguration; + +/** + * TODO + * + * @author Michael H. Siemaszko (mhs@into.software) + * @since Sep 15, 2024 + */ +class InstalledConfigurationImpl implements InstalledConfiguration { + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.InstalledConfiguration#getPid() + */ + @Override + public String getPid() { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.InstalledConfiguration#getFactoryPid() + */ + @Override + public Optional getFactoryPid() { + // TODO Auto-generated method stub + return Optional.empty(); + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.InstalledConfiguration#getProperties() + */ + @Override + public Map getProperties() { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.InstalledConfiguration#getOwningFeatures() + */ + @Override + public List getOwningFeatures() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/com/kentyou/featurelauncher/impl/runtime/InstalledFeatureImpl.java b/src/main/java/com/kentyou/featurelauncher/impl/runtime/InstalledFeatureImpl.java new file mode 100644 index 0000000..9a4663d --- /dev/null +++ b/src/main/java/com/kentyou/featurelauncher/impl/runtime/InstalledFeatureImpl.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl.runtime; + +import java.util.List; + +import org.osgi.service.feature.ID; +import org.osgi.service.featurelauncher.runtime.InstalledBundle; +import org.osgi.service.featurelauncher.runtime.InstalledConfiguration; +import org.osgi.service.featurelauncher.runtime.InstalledFeature; + +/** + * TODO + * + * @author Michael H. Siemaszko (mhs@into.software) + * @since Sep 15, 2024 + */ +class InstalledFeatureImpl implements InstalledFeature { + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.InstalledFeature#getFeatureId() + */ + @Override + public ID getFeatureId() { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.InstalledFeature#isInitialLaunch() + */ + @Override + public boolean isInitialLaunch() { + // TODO Auto-generated method stub + return false; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.InstalledFeature#getInstalledBundles() + */ + @Override + public List getInstalledBundles() { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.InstalledFeature#getInstalledConfigurations() + */ + @Override + public List getInstalledConfigurations() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/com/kentyou/featurelauncher/impl/runtime/RuntimeBundleMergeImpl.java b/src/main/java/com/kentyou/featurelauncher/impl/runtime/RuntimeBundleMergeImpl.java new file mode 100644 index 0000000..85d8620 --- /dev/null +++ b/src/main/java/com/kentyou/featurelauncher/impl/runtime/RuntimeBundleMergeImpl.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl.runtime; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; + +import org.osgi.service.feature.Feature; +import org.osgi.service.feature.FeatureBundle; +import org.osgi.service.featurelauncher.runtime.InstalledBundle; +import org.osgi.service.featurelauncher.runtime.MergeOperationType; +import org.osgi.service.featurelauncher.runtime.RuntimeBundleMerge; + +/** + * TODO + * + * @author Michael H. Siemaszko (mhs@into.software) + * @since Sep 15, 2024 + */ +class RuntimeBundleMergeImpl implements RuntimeBundleMerge { + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.RuntimeBundleMerge#mergeBundle(org.osgi.service.featurelauncher.runtime.MergeOperationType, org.osgi.service.feature.Feature, org.osgi.service.feature.FeatureBundle, java.util.Collection, java.util.List) + */ + @Override + public Stream mergeBundle(MergeOperationType operation, Feature feature, FeatureBundle toMerge, + Collection installedBundles, List existingFeatureBundles) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/com/kentyou/featurelauncher/impl/runtime/RuntimeConfigurationMergeImpl.java b/src/main/java/com/kentyou/featurelauncher/impl/runtime/RuntimeConfigurationMergeImpl.java new file mode 100644 index 0000000..9c9c28b --- /dev/null +++ b/src/main/java/com/kentyou/featurelauncher/impl/runtime/RuntimeConfigurationMergeImpl.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl.runtime; + +import java.util.List; +import java.util.Map; + +import org.osgi.service.feature.Feature; +import org.osgi.service.feature.FeatureConfiguration; +import org.osgi.service.featurelauncher.runtime.InstalledConfiguration; +import org.osgi.service.featurelauncher.runtime.MergeOperationType; +import org.osgi.service.featurelauncher.runtime.RuntimeConfigurationMerge; + +/** + * TODO + * + * @author Michael H. Siemaszko (mhs@into.software) + * @since Sep 15, 2024 + */ +class RuntimeConfigurationMergeImpl implements RuntimeConfigurationMerge { + + /* + * (non-Javadoc) + * @see org.osgi.service.featurelauncher.runtime.RuntimeConfigurationMerge#mergeConfiguration(org.osgi.service.featurelauncher.runtime.MergeOperationType, org.osgi.service.feature.Feature, org.osgi.service.feature.FeatureConfiguration, org.osgi.service.featurelauncher.runtime.InstalledConfiguration, java.util.List) + */ + @Override + public Map mergeConfiguration(MergeOperationType operation, Feature feature, + FeatureConfiguration toMerge, InstalledConfiguration configuration, + List existingFeatureConfigurations) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/com/kentyou/featurelauncher/impl/util/BundleEventUtil.java b/src/main/java/com/kentyou/featurelauncher/impl/util/BundleEventUtil.java new file mode 100644 index 0000000..a279784 --- /dev/null +++ b/src/main/java/com/kentyou/featurelauncher/impl/util/BundleEventUtil.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl.util; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; + +import org.osgi.framework.BundleEvent; + +/** + * Translate type of bundle lifecycle change into readable representation. + * + * Based on: {@link org.ops4j.pax.exam.FrameworkEventUtils} + * + * @since Sep 19, 2024 + */ +public class BundleEventUtil { + private static final Map EVENT_STRINGS; + + static { + EVENT_STRINGS = new HashMap<>(); + for (Field field : BundleEvent.class.getDeclaredFields()) { + if (Modifier.isPublic(field.getModifiers())) { + Integer value; + try { + value = (Integer) field.get(null); + } catch (IllegalAccessException e) { + throw new RuntimeException("Failed to obtain value of BundleEvent." + field.getName()); + } + EVENT_STRINGS.put(value, field.getName()); + } + } + } + + private BundleEventUtil() { + // hidden constructor + } + + /** + * Return a readable representation of the type of a {@link BundleEvent}. + * + * @param bundleEventType a value from {@link BundleEvent#getType()}. + * @return the name of the field that corresponds to this type. + */ + public static String getBundleEventString(int bundleEventType) { + return EVENT_STRINGS.get(bundleEventType); + } +} diff --git a/src/main/java/com/kentyou/featurelauncher/impl/util/BundleStateUtil.java b/src/main/java/com/kentyou/featurelauncher/impl/util/BundleStateUtil.java new file mode 100644 index 0000000..702fb57 --- /dev/null +++ b/src/main/java/com/kentyou/featurelauncher/impl/util/BundleStateUtil.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl.util; + +import org.osgi.framework.Bundle; + +/** + * Translate bundle state into readable representation. + * + * @author Michael H. Siemaszko (mhs@into.software) + * @since Sep 19, 2024 + */ +public class BundleStateUtil { + private BundleStateUtil() { + // hidden constructor + } + + /** + * Return a readable representation of a Bundle state. + * + * A bundle can be in one of six states: + *
    + *
  • {@link #UNINSTALLED}
  • + *
  • {@link #INSTALLED}
  • + *
  • {@link #RESOLVED}
  • + *
  • {@link #STARTING}
  • + *
  • {@link #STOPPING}
  • + *
  • {@link #ACTIVE}
  • + *
+ */ + public static String getBundleStateString(int bundleState) { + switch (bundleState) { + case Bundle.UNINSTALLED: + return "UNINSTALLED"; + case Bundle.INSTALLED: + return "INSTALLED"; + case Bundle.RESOLVED: + return "RESOLVED"; + case Bundle.STARTING: + return "STARTING"; + case Bundle.STOPPING: + return "STOPPING"; + case Bundle.ACTIVE: + return "ACTIVE"; + default: + throw new RuntimeException("Unrecognized Bundle state!"); + } + } +} diff --git a/src/main/java/com/kentyou/featurelauncher/impl/util/FrameworkEventUtil.java b/src/main/java/com/kentyou/featurelauncher/impl/util/FrameworkEventUtil.java new file mode 100644 index 0000000..eb192ef --- /dev/null +++ b/src/main/java/com/kentyou/featurelauncher/impl/util/FrameworkEventUtil.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl.util; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; + +import org.osgi.framework.FrameworkEvent; + +/** + * Translate type of event into readable representation. + * + * Based on: {@link org.ops4j.pax.exam.FrameworkEventUtils} + * + * @since Sep 19, 2024 + */ +public class FrameworkEventUtil { + private static final Map EVENT_STRINGS; + + static { + EVENT_STRINGS = new HashMap<>(); + for (Field field : FrameworkEvent.class.getDeclaredFields()) { + if (Modifier.isPublic(field.getModifiers())) { + Integer value; + try { + value = (Integer) field.get(null); + } catch (IllegalAccessException e) { + throw new RuntimeException("Failed to obtain value of FrameworkEvent." + field.getName()); + } + EVENT_STRINGS.put(value, field.getName()); + } + } + } + + private FrameworkEventUtil() { + // hidden constructor + } + + /** + * Return a readable representation of the type of a {@link FrameworkEvent}. + * + * @param frameworkEventType a value from {@link FrameworkEvent#getType()}. + * @return the name of the field that corresponds to this type. + */ + public static String getFrameworkEventString(int frameworkEventType) { + return EVENT_STRINGS.get(frameworkEventType); + } +} diff --git a/src/main/java/com/kentyou/featurelauncher/impl/util/FrameworkFactoryLocator.java b/src/main/java/com/kentyou/featurelauncher/impl/util/FrameworkFactoryLocator.java new file mode 100644 index 0000000..38c5ca6 --- /dev/null +++ b/src/main/java/com/kentyou/featurelauncher/impl/util/FrameworkFactoryLocator.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl.util; + +import java.util.Optional; +import java.util.ServiceLoader; + +import org.osgi.framework.launch.FrameworkFactory; +import org.osgi.service.featurelauncher.LaunchException; + +/** + * 160.4.3.2: Locating a framework implementation + * + * @author Michael H. Siemaszko (mhs@into.software) + * @since Sep 18, 2024 + */ +public class FrameworkFactoryLocator { + + // TODO: clarify regarding this item + /* + * "160.4.3.2: #1. If any provider specific configuration has been given to the + * Feature Launcher implementation then this should be used to identify the + * framework" + */ + + /* + * "160.4.3.2: #2. If the Feature declares an Extension LAUNCH_FRAMEWORK then + * the Feature Launcher implementation must use the first listed artifact that + * can be found in any configured Artifact Repositories, as described in + * Selecting a framework implementation on page 99" + */ + public static FrameworkFactory selectFrameworkFactory() { + // TODO: 160.4.3.2: #2 + return null; + } + + /* + * "160.4.3.2: #3. If no framework implementation is found in the previous steps + * then the Feature Launcher implementation must search the classpath using the + * Thread Context Class Loader, or, if the Thread Context Class Loader is not + * set, the Class Loader which loaded the caller of the Feature Launcher's + * launch method. The first suitable framework instance located is the instance + * that will be used." + */ + public static FrameworkFactory findFrameworkFactory() { + // TODO: 160.4.3.2: #3 + return null; + } + + /** + * 160.4.3.2: #4. In the event that no suitable OSGi framework can be found by + * any of the previous steps then the Feature Launcher implementation may + * provide a default framework implementation to be used. + * + * @return + */ + public static FrameworkFactory loadDefaultFrameworkFactory() { + ServiceLoader loader = ServiceLoader.load(FrameworkFactory.class); + + Optional frameworkFactoryOptional = loader.findFirst(); + if (frameworkFactoryOptional.isPresent()) { + return frameworkFactoryOptional.get(); + } else { + throw new LaunchException("Error loading default framework factory!"); + } + } +} diff --git a/src/main/resources/META-INF/services/org.osgi.service.featurelauncher.FeatureLauncher b/src/main/resources/META-INF/services/org.osgi.service.featurelauncher.FeatureLauncher new file mode 100644 index 0000000..811c227 --- /dev/null +++ b/src/main/resources/META-INF/services/org.osgi.service.featurelauncher.FeatureLauncher @@ -0,0 +1 @@ +com.kentyou.featurelauncher.impl.FeatureLauncherImpl \ No newline at end of file diff --git a/src/main/resources/META-INF/services/org.osgi.service.featurelauncher.repository.ArtifactRepositoryFactory b/src/main/resources/META-INF/services/org.osgi.service.featurelauncher.repository.ArtifactRepositoryFactory new file mode 100644 index 0000000..811c227 --- /dev/null +++ b/src/main/resources/META-INF/services/org.osgi.service.featurelauncher.repository.ArtifactRepositoryFactory @@ -0,0 +1 @@ +com.kentyou.featurelauncher.impl.FeatureLauncherImpl \ No newline at end of file diff --git a/src/test/java/com/kentyou/featurelauncher/impl/FeatureLauncherImplTest.java b/src/test/java/com/kentyou/featurelauncher/impl/FeatureLauncherImplTest.java new file mode 100644 index 0000000..84afce1 --- /dev/null +++ b/src/test/java/com/kentyou/featurelauncher/impl/FeatureLauncherImplTest.java @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; +import java.util.ServiceLoader; + +import org.junit.Before; +import org.junit.Test; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleException; +import org.osgi.framework.launch.Framework; +import org.osgi.service.featurelauncher.FeatureLauncher; +import org.osgi.service.featurelauncher.repository.ArtifactRepository; + +import com.kentyou.featurelauncher.impl.util.BundleStateUtil; + +/** + * Tests {@link com.kentyou.featurelauncher.impl.FeatureLauncherImpl} + * + * @author Michael H. Siemaszko (mhs@into.software) + * @since Sep 17, 2024 + */ +public class FeatureLauncherImplTest { + FeatureLauncher featureLauncher; + + @Before + public void setUp() { + // Load the Feature Launcher + ServiceLoader loader = ServiceLoader.load(FeatureLauncher.class); + Optional featureLauncherOptional = loader.findFirst(); + + if (featureLauncherOptional.isPresent()) { + featureLauncher = featureLauncherOptional.get(); + } else { + throw new IllegalStateException("Error loading feature launcher!"); + } + } + + @Test + public void testLaunchDefaultFrameworkWithLocalArtifactRepository() + throws IOException, InterruptedException, URISyntaxException, BundleException { + // Set up a repository + File userHome = new File(System.getProperty("user.home")); + Path localM2RepositoryPath = Paths.get(userHome.getCanonicalPath(), ".m2", "repository"); + + ArtifactRepository localArtifactRepository = featureLauncher.createRepository(localM2RepositoryPath); + assertNotNull(localArtifactRepository); + + // Read Feature JSON + Path featureJSONPath = Paths.get(getClass().getResource("/features/gogo-console-feature.json").toURI()); + + // Launch the framework + Framework osgiFramework = featureLauncher.launch(Files.newBufferedReader(featureJSONPath)) + .withRepository(localArtifactRepository).launchFramework(); + + // Verify bundles defined in feature are installed and started + Bundle[] bundles = osgiFramework.getBundleContext().getBundles(); + assertEquals(4, bundles.length); + + assertEquals("org.apache.felix.gogo.command", bundles[1].getSymbolicName()); + assertEquals("ACTIVE", BundleStateUtil.getBundleStateString(bundles[1].getState())); + + assertEquals("org.apache.felix.gogo.shell", bundles[2].getSymbolicName()); + assertEquals("ACTIVE", BundleStateUtil.getBundleStateString(bundles[2].getState())); + + assertEquals("org.apache.felix.gogo.runtime", bundles[3].getSymbolicName()); + assertEquals("ACTIVE", BundleStateUtil.getBundleStateString(bundles[3].getState())); + + osgiFramework.stop(); + } +} diff --git a/src/test/java/com/kentyou/featurelauncher/impl/repository/LocalArtifactRepositoryImplTest.java b/src/test/java/com/kentyou/featurelauncher/impl/repository/LocalArtifactRepositoryImplTest.java new file mode 100644 index 0000000..d7b0711 --- /dev/null +++ b/src/test/java/com/kentyou/featurelauncher/impl/repository/LocalArtifactRepositoryImplTest.java @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2012 - 2024 Data In Motion 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: + * Data In Motion - initial API and implementation + */ +package com.kentyou.featurelauncher.impl.repository; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; +import java.util.ServiceLoader; +import java.util.UUID; + +import org.apache.felix.feature.impl.IDImpl; +import org.junit.Before; +import org.junit.Test; +import org.osgi.service.featurelauncher.FeatureLauncher; +import org.osgi.service.featurelauncher.repository.ArtifactRepository; + +/** + * Tests + * {@link com.kentyou.featurelauncher.impl.repository.LocalArtifactRepositoryImpl} + * and + * {@link com.kentyou.featurelauncher.impl.repository.ArtifactRepositoryFactoryImpl} + * + * @author Michael H. Siemaszko (mhs@into.software) + * @since Sep 17, 2024 + */ +public class LocalArtifactRepositoryImplTest { + FeatureLauncher featureLauncher; + + @Before + public void setUp() { + // Load the Feature Launcher + ServiceLoader loader = ServiceLoader.load(FeatureLauncher.class); + Optional featureLauncherOptional = loader.findFirst(); + + if (featureLauncherOptional.isPresent()) { + featureLauncher = featureLauncherOptional.get(); + } else { + throw new IllegalStateException("Error loading feature launcher!"); + } + } + + @Test + public void testCreateLocalArtifactRepository() throws IOException { + File userHome = new File(System.getProperty("user.home")); + + Path localM2RepositoryPath = Paths.get(userHome.getCanonicalPath(), ".m2", "repository"); + + ArtifactRepository localArtifactRepository = featureLauncher.createRepository(localM2RepositoryPath); + + assertNotNull(localArtifactRepository); + assertTrue(localArtifactRepository instanceof LocalArtifactRepositoryImpl); + } + + @Test + public void testCreateLocalArtifactRepositoryNullPath() { + assertThrows(NullPointerException.class, () -> featureLauncher.createRepository(null)); + } + + @Test + public void testCreateLocalArtifactRepositoryPathDoesNotExist() throws IOException { + Path nonExistingRepositoryPath = Paths.get(FileSystems.getDefault().getSeparator(), "tmp", + UUID.randomUUID().toString()); + + assertThrows(IllegalArgumentException.class, () -> featureLauncher.createRepository(nonExistingRepositoryPath)); + } + + @Test + public void testCreateLocalArtifactRepositoryPathNotADirectory() throws IOException { + File tmpFile = File.createTempFile("localArtifactRepositoryTest", "tmp"); + tmpFile.deleteOnExit(); + + assertThrows(IllegalArgumentException.class, () -> featureLauncher.createRepository(tmpFile.toPath())); + } + + @Test + public void testGetArtifactFromLocalArtifactRepository() throws IOException { + File userHome = new File(System.getProperty("user.home")); + + Path localM2RepositoryPath = Paths.get(userHome.getCanonicalPath(), ".m2", "repository"); + + ArtifactRepository localArtifactRepository = featureLauncher.createRepository(localM2RepositoryPath); + + assertNotNull(localArtifactRepository); + assertTrue(localArtifactRepository instanceof LocalArtifactRepositoryImpl); + + IDImpl artifactId = IDImpl.fromMavenID("org.osgi:org.osgi.service.feature:1.0.0"); + assertNotNull(artifactId); + + try (InputStream is = localArtifactRepository.getArtifact(artifactId)) { + + File tmpFile = File.createTempFile("artifactFromLocalArtifactRepositoryTest", "tmp"); + tmpFile.deleteOnExit(); + + try (OutputStream tmpFileOutputStream = new FileOutputStream(tmpFile)) { + tmpFileOutputStream.write(is.readAllBytes()); + + assertEquals(37779, tmpFile.length()); + } + } + } +} diff --git a/src/test/resources/features/gogo-console-feature.json b/src/test/resources/features/gogo-console-feature.json new file mode 100644 index 0000000..5900284 --- /dev/null +++ b/src/test/resources/features/gogo-console-feature.json @@ -0,0 +1,15 @@ +{ + "id": "com.kentyou.featurelauncher:gogo-console-feature:1.0", + "description": "gogo console feature", + "bundles": [ + { + "id": "org.apache.felix:org.apache.felix.gogo.command:1.1.2" + }, + { + "id": "org.apache.felix:org.apache.felix.gogo.shell:1.1.4" + }, + { + "id": "org.apache.felix:org.apache.felix.gogo.runtime:1.1.6" + } + ] +} From b4bc4d2eedc9cc2c23faa75a54fd12a9147e90db Mon Sep 17 00:00:00 2001 From: "Michael H. Siemaszko" Date: Sun, 22 Sep 2024 01:57:32 +0200 Subject: [PATCH 2/2] Implementation of "160. Feature Launcher Service Specification" (c.d.) - Applied changes requested in PR review: * Fix copyright headers and year - previously used those from code templates defined in Eclipse via Gecko IDE * Link 160 specification sections in integration tests like in implementation classes * Use dedicated M2 repository instead of system-wide local M2 repository * Assert based on "Bundle-SymbolicName" in '#testGetArtifactFromLocalArtifactRepository' test case * Allow multiple artifact repositories be added to 'FeatureLauncher.LaunchBuilder' Signed-off-by: Michael H. Siemaszko --- pom.xml | 84 +++++++++++++++---- .../impl/FeatureLauncherImpl.java | 45 ++++++---- .../DecoratorBuilderFactoryImpl.java | 4 +- .../impl/decorator/FeatureDecoratorImpl.java | 4 +- .../FeatureExtensionHandlerImpl.java | 4 +- .../ArtifactRepositoryFactoryImpl.java | 4 +- .../LocalArtifactRepositoryImpl.java | 21 +++-- .../RemoteArtifactRepositoryImpl.java | 4 +- .../impl/runtime/FeatureRuntimeImpl.java | 4 +- .../impl/runtime/InstalledBundleImpl.java | 4 +- .../runtime/InstalledConfigurationImpl.java | 4 +- .../impl/runtime/InstalledFeatureImpl.java | 4 +- .../impl/runtime/RuntimeBundleMergeImpl.java | 4 +- .../RuntimeConfigurationMergeImpl.java | 4 +- .../impl/util/BundleEventUtil.java | 4 +- .../impl/util/BundleStateUtil.java | 4 +- .../impl/util/FrameworkEventUtil.java | 4 +- .../impl/util/FrameworkFactoryLocator.java | 4 +- .../impl/FeatureLauncherImplTest.java | 20 +++-- .../LocalArtifactRepositoryImplTest.java | 45 +++++----- 20 files changed, 178 insertions(+), 97 deletions(-) diff --git a/pom.xml b/pom.xml index b2fa46c..a538854 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,7 @@ UTF-8 17 + ${project.build.directory}/m2Repo @@ -78,17 +79,17 @@ org.apache.felix.feature 1.0.2 - - org.slf4j - slf4j-api - 2.0.9 - - - org.slf4j - slf4j-simple - 2.0.9 - - + + org.slf4j + slf4j-api + 2.0.9 + + + org.slf4j + slf4j-simple + 2.0.9 + + junit @@ -108,21 +109,21 @@ 1.2.19 jakarta test - + org.junit.jupiter junit-jupiter-api test - + - org.apache.felix - org.apache.felix.framework - 7.0.5 - test + org.apache.felix + org.apache.felix.framework + 7.0.5 + test - + org.apache.felix @@ -222,6 +223,53 @@ + + org.apache.maven.plugins + maven-dependency-plugin + 3.8.0 + + + copy-dependencies + generate-test-resources + + copy-dependencies + + + ${m2RepoPath} + true + true + + + + + + org.codehaus.mojo + properties-maven-plugin + 1.2.1 + + + + set-system-properties + + + + + M2_REPO_PATH + ${m2RepoPath} + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + 0 + +
diff --git a/src/main/java/com/kentyou/featurelauncher/impl/FeatureLauncherImpl.java b/src/main/java/com/kentyou/featurelauncher/impl/FeatureLauncherImpl.java index c74c067..1c47771 100644 --- a/src/main/java/com/kentyou/featurelauncher/impl/FeatureLauncherImpl.java +++ b/src/main/java/com/kentyou/featurelauncher/impl/FeatureLauncherImpl.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2012 - 2024 Data In Motion and others. + * Copyright (c) 2024 Kentyou and others. * All rights reserved. * * This program and the accompanying materials are made @@ -9,7 +9,7 @@ * SPDX-License-Identifier: EPL-2.0 * * Contributors: - * Data In Motion - initial API and implementation + * Kentyou - initial implementation */ package com.kentyou.featurelauncher.impl; @@ -95,7 +95,7 @@ class LaunchBuilderImpl implements LaunchBuilder { private final Feature feature; private boolean isLaunched; private List installedBundles; - private ArtifactRepository artifactRepository; + private List artifactRepositories; private Map configuration; private Map variables; private Map frameworkProps; @@ -108,6 +108,7 @@ class LaunchBuilderImpl implements LaunchBuilder { this.feature = feature; this.isLaunched = false; this.installedBundles = new ArrayList<>(); + this.artifactRepositories = new ArrayList<>(); this.configuration = new HashMap<>(); this.variables = new HashMap<>(); this.frameworkProps = new HashMap<>(); @@ -125,7 +126,7 @@ public LaunchBuilder withRepository(ArtifactRepository repository) { ensureNotLaunchedYet(); - this.artifactRepository = repository; + this.artifactRepositories.add(repository); return this; } @@ -213,7 +214,11 @@ public LaunchBuilder withExtensionHandler(String extensionName, FeatureExtension @Override public Framework launchFramework() { Objects.requireNonNull(feature, "Feature is required!"); - Objects.requireNonNull(artifactRepository, "Artifact Repository is required!"); + + if (this.artifactRepositories.isEmpty()) { + LOG.error("At least one Artifact Repository is required!"); + throw new NullPointerException("At least one Artifact Repository is required!"); + } ensureNotLaunchedYet(); @@ -229,11 +234,8 @@ public Framework launchFramework() { /////////////////////////////////////////// // 160.4.3.3: Creating a Framework instance - Framework framework = createFramework(frameworkFactory, Collections.emptyMap()); - addLogListeners(framework); - ///////////////////////////////////////////////////////// // 160.4.3.4: Installing bundles and configurations installBundles(framework); @@ -253,6 +255,9 @@ private Framework createFramework(FrameworkFactory frameworkFactory, Map loader = ServiceLoader.load(FeatureLauncher.class); Optional featureLauncherOptional = loader.findFirst(); @@ -61,9 +72,6 @@ public void setUp() { public void testLaunchDefaultFrameworkWithLocalArtifactRepository() throws IOException, InterruptedException, URISyntaxException, BundleException { // Set up a repository - File userHome = new File(System.getProperty("user.home")); - Path localM2RepositoryPath = Paths.get(userHome.getCanonicalPath(), ".m2", "repository"); - ArtifactRepository localArtifactRepository = featureLauncher.createRepository(localM2RepositoryPath); assertNotNull(localArtifactRepository); diff --git a/src/test/java/com/kentyou/featurelauncher/impl/repository/LocalArtifactRepositoryImplTest.java b/src/test/java/com/kentyou/featurelauncher/impl/repository/LocalArtifactRepositoryImplTest.java index d7b0711..e74d4f5 100644 --- a/src/test/java/com/kentyou/featurelauncher/impl/repository/LocalArtifactRepositoryImplTest.java +++ b/src/test/java/com/kentyou/featurelauncher/impl/repository/LocalArtifactRepositoryImplTest.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2012 - 2024 Data In Motion and others. + * Copyright (c) 2024 Kentyou and others. * All rights reserved. * * This program and the accompanying materials are made @@ -9,7 +9,7 @@ * SPDX-License-Identifier: EPL-2.0 * * Contributors: - * Data In Motion - initial API and implementation + * Kentyou - initial implementation */ package com.kentyou.featurelauncher.impl.repository; @@ -19,16 +19,16 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; import java.util.ServiceLoader; import java.util.UUID; +import java.util.jar.Attributes; +import java.util.jar.JarInputStream; +import java.util.jar.Manifest; import org.apache.felix.feature.impl.IDImpl; import org.junit.Before; @@ -42,14 +42,26 @@ * and * {@link com.kentyou.featurelauncher.impl.repository.ArtifactRepositoryFactoryImpl} * + * As defined in: "160.2.1.2 Local Repositories" + * * @author Michael H. Siemaszko (mhs@into.software) * @since Sep 17, 2024 */ public class LocalArtifactRepositoryImplTest { + private static final String M2_REPO_PROP_KEY = "M2_REPO_PATH"; // TODO: move to common constants + FeatureLauncher featureLauncher; + Path localM2RepositoryPath; @Before public void setUp() { + // Obtain path of dedicated M2 repo + if (System.getProperty(M2_REPO_PROP_KEY) == null) { + throw new IllegalStateException("M2 repository is not defined!"); + } + + localM2RepositoryPath = Paths.get(System.getProperty(M2_REPO_PROP_KEY)); + // Load the Feature Launcher ServiceLoader loader = ServiceLoader.load(FeatureLauncher.class); Optional featureLauncherOptional = loader.findFirst(); @@ -63,10 +75,6 @@ public void setUp() { @Test public void testCreateLocalArtifactRepository() throws IOException { - File userHome = new File(System.getProperty("user.home")); - - Path localM2RepositoryPath = Paths.get(userHome.getCanonicalPath(), ".m2", "repository"); - ArtifactRepository localArtifactRepository = featureLauncher.createRepository(localM2RepositoryPath); assertNotNull(localArtifactRepository); @@ -96,10 +104,6 @@ public void testCreateLocalArtifactRepositoryPathNotADirectory() throws IOExcept @Test public void testGetArtifactFromLocalArtifactRepository() throws IOException { - File userHome = new File(System.getProperty("user.home")); - - Path localM2RepositoryPath = Paths.get(userHome.getCanonicalPath(), ".m2", "repository"); - ArtifactRepository localArtifactRepository = featureLauncher.createRepository(localM2RepositoryPath); assertNotNull(localArtifactRepository); @@ -108,16 +112,13 @@ public void testGetArtifactFromLocalArtifactRepository() throws IOException { IDImpl artifactId = IDImpl.fromMavenID("org.osgi:org.osgi.service.feature:1.0.0"); assertNotNull(artifactId); - try (InputStream is = localArtifactRepository.getArtifact(artifactId)) { - - File tmpFile = File.createTempFile("artifactFromLocalArtifactRepositoryTest", "tmp"); - tmpFile.deleteOnExit(); - - try (OutputStream tmpFileOutputStream = new FileOutputStream(tmpFile)) { - tmpFileOutputStream.write(is.readAllBytes()); + try (JarInputStream jarIs = new JarInputStream(localArtifactRepository.getArtifact(artifactId))) { + Manifest jarMf = jarIs.getManifest(); + assertTrue(jarMf != null); - assertEquals(37779, tmpFile.length()); - } + Attributes jarAttributes = jarMf.getMainAttributes(); + assertTrue(jarAttributes != null); + assertEquals("org.osgi.service.feature", jarAttributes.getValue("Bundle-SymbolicName")); } } }