Skip to content

Commit

Permalink
feat: loader for extensions in custom base packages (#2081)
Browse files Browse the repository at this point in the history
* extensions loader

Signed-off-by: Pablo Hernán Carle <[email protected]>

* unit tests

Signed-off-by: Pablo Hernán Carle <[email protected]>

* logs

Signed-off-by: Pablo Hernán Carle <[email protected]>

* fix typo and fixes

Signed-off-by: Pablo Hernán Carle <[email protected]>

* public modifier

Signed-off-by: Pablo Hernán Carle <[email protected]>

* coverage

Signed-off-by: Pablo Hernán Carle <[email protected]>

* jupiter

Signed-off-by: Pablo Hernán Carle <[email protected]>

* tests

Signed-off-by: Pablo Hernán Carle <[email protected]>

* test

Signed-off-by: Pablo Hernán Carle <[email protected]>

* add case and logs

Signed-off-by: Pablo Hernán Carle <[email protected]>

* fix deserialization

Signed-off-by: Pablo Hernán Carle <[email protected]>

* remove log

Signed-off-by: Pablo Hernán Carle <[email protected]>

* simplify constructors

Signed-off-by: Pablo Hernán Carle <[email protected]>

* pr review

Signed-off-by: Pablo Hernán Carle <[email protected]>

* refactor tests

Signed-off-by: Pablo Hernán Carle <[email protected]>

Co-authored-by: Pablo Hernán Carle <[email protected]>
  • Loading branch information
2 people authored and JirkaAichler committed Mar 7, 2022
1 parent 4fabe1c commit f988f1b
Show file tree
Hide file tree
Showing 16 changed files with 592 additions and 4 deletions.
14 changes: 14 additions & 0 deletions apiml-extension-loader/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
dependencies {
compile(project(':common-service-core'))
compileOnly libraries.spring_boot_starter_web

implementation libraries.jackson_dataformat_yaml

compileOnly libraries.lombok
annotationProcessor libraries.lombok

testImplementation libraries.spring_boot_starter_test

testCompileOnly libraries.lombok
testAnnotationProcessor libraries.lombok
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/
package org.zowe.apiml.extension;

import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;

import org.zowe.apiml.extension.ExtensionDefinition.ApimlServices;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ExtensionConfigReader {

private final ZoweRuntimeEnvironment environment;

public ExtensionConfigReader(ZoweRuntimeEnvironment environment) {
this.environment = environment;
}

public String[] getBasePackages() {
return getEnabledExtensions()
.stream()
.map(ExtensionDefinition::getApimlServices)
.filter(Objects::nonNull)
.map(ApimlServices::getBasePackage)
.filter(Objects::nonNull)
.collect(Collectors.toList())
.toArray(new String[0]);
}

private List<ExtensionDefinition> getEnabledExtensions() {
List<String> installedComponents = environment.getInstalledComponents();
List<String> enabledComponents = environment.getEnabledComponents();
List<ExtensionDefinition> extensions = new ArrayList<>();
ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()).configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
ObjectMapper jsonMapper = new ObjectMapper().configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

for (String installedComponent : installedComponents) {
if (enabledComponents.contains(installedComponent)) {
String parentPath = environment.getExtensionDirectory() + File.separator + installedComponent;
Path manifestYamlPath = Paths.get(parentPath + "/manifest.yaml");
Path manifestJsonPath = Paths.get(parentPath + "/manifest.json");
try {
if (Files.exists(manifestYamlPath)) {
extensions.add(yamlMapper.readValue(Files.readAllBytes(manifestYamlPath), ExtensionDefinition.class));
} else if (Files.exists(manifestJsonPath)) {
extensions.add(jsonMapper.readValue(Files.readAllBytes(manifestJsonPath), ExtensionDefinition.class));
}
} catch (Exception e) {
log.error("Failed reading component manifests", e);
}
}
}
return extensions;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/
package org.zowe.apiml.extension;

import lombok.Data;
import lombok.NoArgsConstructor;

/**
* Definition based on https://docs.zowe.org/stable/extend/packaging-zos-extensions/#zowe-component-manifest
*/
@Data
@NoArgsConstructor
public class ExtensionDefinition {

private String name;
private ApimlServices apimlServices;

@Data
@NoArgsConstructor
public static class ApimlServices {

private String basePackage;

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/
package org.zowe.apiml.extension;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.context.event.ApplicationContextInitializedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;

import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
* Loader of extensions
*
*/
@Slf4j
@RequiredArgsConstructor
public class ExtensionsLoader implements ApplicationListener<ApplicationContextInitializedEvent> {

@NonNull
private final ExtensionConfigReader configReader;

@Override
public void onApplicationEvent(ApplicationContextInitializedEvent event) {
if (!(event.getApplicationContext() instanceof BeanDefinitionRegistry)) {
log.error("Expected Spring context to be a BeanDefinitionRegistry. Extensions are not loaded");
} else {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) event.getApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry);

String[] extensionsBasePackages = configReader.getBasePackages();
if (extensionsBasePackages.length > 0) {
try {
scanner.scan(configReader.getBasePackages());

String[] beanNames = scanner.getRegistry().getBeanDefinitionNames();
for (String name : beanNames) {
if (!registry.containsBeanDefinition(name)) {
registry.registerBeanDefinition(name, scanner.getRegistry().getBeanDefinition(name));
} else {
log.info("Bean with name " + name + " is already registered in the context");
}
}
} catch (Exception e) {
log.error("Failed loading extensions", e);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/
package org.zowe.apiml.extension;

import static java.util.Collections.emptyList;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import org.apache.commons.lang3.StringUtils;

import lombok.NoArgsConstructor;

@NoArgsConstructor
public class ZoweRuntimeEnvironment {

static final String INSTALLED_EXTENSIONS_ENV = "ZWE_INSTALLED_COMPONENTS";
static final String ENABLED_EXTENSIONS_ENV = "ZWE_ENABLED_COMPONENTS";

static final String WORKSPACE_DIR_ENV = "ZWE_zowe_workspaceDirectory";
static final String COMPONENTS_APP_SERVER_DIR_ENV = "ZWE_components_app_server_pluginsDir";
static final String PLUGINS_DIR_ENV = "ZWED_pluginsDir";
static final String EXTENSION_DIR_ENV = "ZWE_zowe_extensionDirectory";

public static ZoweRuntimeEnvironment defaultEnv() {
return new ZoweRuntimeEnvironment();
}

Optional<String> getPluginsDir() {
String pluginsDir = System.getenv(PLUGINS_DIR_ENV);
String componentsAppServerPluginsDir = System.getenv(COMPONENTS_APP_SERVER_DIR_ENV);
String workspaceDir = System.getenv(WORKSPACE_DIR_ENV);

if (StringUtils.isNotEmpty(pluginsDir)) {
return Optional.of(pluginsDir);
} else if (StringUtils.isNotEmpty(componentsAppServerPluginsDir)) {
return Optional.of(componentsAppServerPluginsDir);
} else if (StringUtils.isNotEmpty(workspaceDir)) {
return Optional.of(workspaceDir);
}
return Optional.empty();
}

private List<String> getComponents(String env) {
return Optional.ofNullable(env)
.map(installed -> installed.split(","))
.map(Arrays::asList)
.orElse(emptyList());
}

List<String> getInstalledComponents() {
return getComponents(System.getenv(INSTALLED_EXTENSIONS_ENV));
}

List<String> getEnabledComponents() {
return getComponents(System.getenv(ENABLED_EXTENSIONS_ENV));
}

String getExtensionDirectory() {
return System.getenv(EXTENSION_DIR_ENV);
}
}
21 changes: 21 additions & 0 deletions apiml-extension-loader/src/test/java/org/zowe/CustomBean.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/
package org.zowe;

import org.springframework.stereotype.Component;

/**
* Intended as a test bean located outside of the default component scan
*
*/
@Component
public class CustomBean {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/
package org.zowe.apiml.extension;

import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.mockito.Mockito.when;

import java.util.Collections;

import com.google.common.io.Resources;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class ExtensionConfigReaderTest {

@Mock
private ZoweRuntimeEnvironment environment;

private ExtensionConfigReader configReader;

@BeforeEach
public void setUp() {
this.configReader = new ExtensionConfigReader(environment);
}

private String getTestResourcesPath() {
String path = Resources.getResource("apimlextension").getPath();
return path.substring(0, path.lastIndexOf('/'));
}

@Nested
class WhenGettingExtensionsConfiguration {

@Nested
class GivenNoManifestIsAvailable {
@Test
void itReturnsNoPackagesToScan() {
when(environment.getExtensionDirectory()).thenReturn(".");
when(environment.getInstalledComponents()).thenReturn(singletonList("apimlextension"));
when(environment.getEnabledComponents()).thenReturn(singletonList("apimlextension"));

assertArrayEquals(new String[]{}, configReader.getBasePackages());
}
}

@Nested
class GivenAnExtensionIsDefined {
@Test
void itReturnsPackageNameToScan() {
when(environment.getExtensionDirectory()).thenReturn(getTestResourcesPath());
when(environment.getInstalledComponents()).thenReturn(singletonList("apimlextension"));
when(environment.getEnabledComponents()).thenReturn(singletonList("apimlextension"));

assertArrayEquals(new String[]{ "org.zowe" }, configReader.getBasePackages());
}
}

@Nested
class GivenNoComponentsAreInstalled {
@Test
void itReturnsNoPackagesToScan() {
when(environment.getInstalledComponents()).thenReturn(Collections.emptyList());
when(environment.getEnabledComponents()).thenReturn(Collections.emptyList());

assertArrayEquals(new String[]{}, configReader.getBasePackages());
}
}
}
}
Loading

0 comments on commit f988f1b

Please sign in to comment.