Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support plugin management and dynamic plugin installation with extensions #984

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import com.alipay.sofa.ark.spi.event.biz.BeforeBizStopEvent;
import com.alipay.sofa.ark.spi.model.Biz;
import com.alipay.sofa.ark.spi.model.BizState;
import com.alipay.sofa.ark.spi.model.Plugin;
import com.alipay.sofa.ark.spi.service.biz.BizManagerService;
import com.alipay.sofa.ark.spi.service.event.EventAdminService;

Expand All @@ -46,6 +47,7 @@
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
Expand Down Expand Up @@ -111,6 +113,8 @@ public class BizModel implements Biz {

private List<BizStateRecord> bizStateRecords = new CopyOnWriteArrayList<>();

private Set<Plugin> dependentPlugins = new HashSet<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider Thread Safety for dependentPlugins

If dependentPlugins may be accessed by multiple threads concurrently, consider using a thread-safe collection like CopyOnWriteArraySet or synchronizing access appropriately to prevent concurrent modification issues.


public BizModel setBizName(String bizName) {
AssertUtils.isFalse(StringUtils.isEmpty(bizName), "Biz Name must not be empty!");
this.bizName = bizName;
Expand Down Expand Up @@ -229,6 +233,15 @@ private void addStateChangeLog(StateChangeReason reason, String message) {
bizStateRecords.add(new BizStateRecord(new Date(), bizState, reason, message));
}

public Set<Plugin> getDependentPlugins() {
return dependentPlugins;
}
Comment on lines +236 to +238
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Return Unmodifiable Set in getDependentPlugins()

Returning the mutable dependentPlugins set directly allows external code to modify the internal state of BizModel. To preserve encapsulation, consider returning an unmodifiable view of the set.

Example:

public Set<Plugin> getDependentPlugins() {
-    return dependentPlugins;
+    return Collections.unmodifiableSet(dependentPlugins);
}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public Set<Plugin> getDependentPlugins() {
return dependentPlugins;
}
public Set<Plugin> getDependentPlugins() {
return Collections.unmodifiableSet(dependentPlugins);
}


public BizModel setDependentPlugins(Set<Plugin> dependentPlugins) {
this.dependentPlugins = dependentPlugins;
return this;
}
Comment on lines +240 to +243
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make a Defensive Copy in setDependentPlugins()

Assigning the provided set directly to dependentPlugins may lead to unintended modifications if the caller alters the set after passing it. Create a defensive copy to safeguard internal state.

Example:

public BizModel setDependentPlugins(Set<Plugin> dependentPlugins) {
-    this.dependentPlugins = dependentPlugins;
+    this.dependentPlugins = new HashSet<>(dependentPlugins);
    return this;
}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public BizModel setDependentPlugins(Set<Plugin> dependentPlugins) {
this.dependentPlugins = dependentPlugins;
return this;
}
public BizModel setDependentPlugins(Set<Plugin> dependentPlugins) {
this.dependentPlugins = new HashSet<>(dependentPlugins);
return this;
}


@Override
public String getBizName() {
return bizName;
Expand Down Expand Up @@ -617,4 +630,39 @@ private static String markBizTempWorkDirRecycled(File bizTempWorkDir) throws IOE

return targetPath;
}

/* export class and classloader relationship cache */
private ConcurrentHashMap<String, Plugin> exportClassAndClassLoaderMap = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, Plugin> exportNodeAndClassLoaderMap = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, Plugin> exportStemAndClassLoaderMap = new ConcurrentHashMap<>();

/* export cache and classloader relationship cache */
private ConcurrentHashMap<String, List<Plugin>> exportResourceAndClassLoaderMap = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, List<Plugin>> exportPrefixStemResourceAndClassLoaderMap = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, List<Plugin>> exportSuffixStemResourceAndClassLoaderMap = new ConcurrentHashMap<>();

public ConcurrentHashMap<String, Plugin> getExportClassAndClassLoaderMap() {
return exportClassAndClassLoaderMap;
}

public ConcurrentHashMap<String, Plugin> getExportNodeAndClassLoaderMap() {
return exportNodeAndClassLoaderMap;
}

public ConcurrentHashMap<String, Plugin> getExportStemAndClassLoaderMap() {
return exportStemAndClassLoaderMap;
}

public ConcurrentHashMap<String, List<Plugin>> getExportResourceAndClassLoaderMap() {
return exportResourceAndClassLoaderMap;
}

public ConcurrentHashMap<String, List<Plugin>> getExportPrefixStemResourceAndClassLoaderMap() {
return exportPrefixStemResourceAndClassLoaderMap;
}

public ConcurrentHashMap<String, List<Plugin>> getExportSuffixStemResourceAndClassLoaderMap() {
return exportSuffixStemResourceAndClassLoaderMap;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.alipay.sofa.ark.spi.service.biz.BizManagerService;
import com.alipay.sofa.ark.spi.service.event.EventAdminService;
import com.alipay.sofa.ark.spi.service.injection.InjectionService;
import com.alipay.sofa.ark.spi.service.plugin.PluginFactoryService;
import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService;
import com.google.inject.Binding;
import com.google.inject.Guice;
Expand Down Expand Up @@ -92,6 +93,7 @@ public void start() throws ArkRuntimeException {
ArkClient.setInjectionService(getService(InjectionService.class));
ArkClient.setEventAdminService(getService(EventAdminService.class));
ArkClient.setPluginManagerService(getService(PluginManagerService.class));
ArkClient.setPluginFactoryService(getService(PluginFactoryService.class));
ArkClient.setArguments(arguments);
ArkLoggerFactory.getDefaultLogger().info("Finish to start ArkServiceContainer");
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,8 @@
import com.alipay.sofa.ark.spi.archive.Archive;
import com.alipay.sofa.ark.spi.archive.BizArchive;
import com.alipay.sofa.ark.spi.constant.Constants;
import com.alipay.sofa.ark.spi.model.Biz;
import com.alipay.sofa.ark.spi.model.*;
import com.alipay.sofa.ark.spi.model.BizInfo.StateChangeReason;
import com.alipay.sofa.ark.spi.model.BizOperation;
import com.alipay.sofa.ark.spi.model.BizState;
import com.alipay.sofa.ark.spi.model.Plugin;
import com.alipay.sofa.ark.spi.service.biz.BizFactoryService;
import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService;
import com.google.inject.Inject;
Expand All @@ -48,9 +45,11 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.stream.Stream;

import static com.alipay.sofa.ark.spi.constant.Constants.ARK_BIZ_NAME;
import static com.alipay.sofa.ark.spi.constant.Constants.ARK_BIZ_VERSION;
Expand Down Expand Up @@ -80,15 +79,60 @@ public class BizFactoryServiceImpl implements BizFactoryService {

@Override
public Biz createBiz(BizArchive bizArchive) throws IOException {
return createBiz(bizArchive, new BizConfig());
}

@Override
public Biz createBiz(BizArchive bizArchive, URL[] extensionUrls) throws IOException {
BizConfig bizConfig = new BizConfig();
bizConfig.setExtensionUrls(extensionUrls);
return createBiz(bizArchive, bizConfig);
}

@Override
public Biz createBiz(File file) throws IOException {
BizArchive bizArchive = prepareBizArchive(file);
return createBiz(bizArchive, new BizConfig());
}

@Override
public Biz createBiz(File file, URL[] extensionUrls) throws IOException {
yuanyuancin marked this conversation as resolved.
Show resolved Hide resolved
BizArchive bizArchive = prepareBizArchive(file);
BizConfig bizConfig = new BizConfig();
bizConfig.setExtensionUrls(extensionUrls);
return createBiz(bizArchive, bizConfig);
}

Comment on lines +85 to +105
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider refactoring overloaded createBiz methods to reduce code duplication

The overloaded createBiz methods between lines 85 and 105 have similar implementations where a BizConfig is created, configured, and then passed to another createBiz method. This code duplication can be reduced for better maintainability.

Consider refactoring to eliminate duplication, possibly by introducing helper methods or utilizing default parameter values.

@Override
public Biz createBiz(BizOperation bizOperation, File file) throws IOException {
BizArchive bizArchive = prepareBizArchive(file);
BizConfig bizConfig = new BizConfig();
bizConfig.setSpecifiedVersion(bizOperation.getBizVersion());
return createBiz(bizArchive, bizConfig);
}

@Override
public Biz createBiz(File file, BizConfig bizConfig) throws IOException {
BizArchive bizArchive = prepareBizArchive(file);
return createBiz(bizArchive, bizConfig);
}

@Override
public Biz createBiz(BizArchive bizArchive, BizConfig bizConfig) throws IOException {
AssertUtils.isTrue(isArkBiz(bizArchive), "Archive must be a ark biz!");
BizModel bizModel = new BizModel();
AssertUtils.isTrue(bizConfig != null, "BizConfig must not be null!");

Attributes manifestMainAttributes = bizArchive.getManifest().getMainAttributes();
String mainClass = manifestMainAttributes.getValue(MAIN_CLASS_ATTRIBUTE);
String startClass = manifestMainAttributes.getValue(START_CLASS_ATTRIBUTE);
BizModel bizModel = new BizModel();
bizModel
.setBizState(BizState.RESOLVED, StateChangeReason.CREATED)
.setBizName(manifestMainAttributes.getValue(ARK_BIZ_NAME))
.setBizVersion(manifestMainAttributes.getValue(ARK_BIZ_VERSION))
.setBizVersion(
!StringUtils.isEmpty(bizConfig.getSpecifiedVersion()) ? bizConfig
yuanyuancin marked this conversation as resolved.
Show resolved Hide resolved
.getSpecifiedVersion() : manifestMainAttributes.getValue(ARK_BIZ_VERSION))
.setBizUrl(!(bizArchive instanceof DirectoryBizArchive) ? bizArchive.getUrl() : null)
.setMainClass(!StringUtils.isEmpty(startClass) ? startClass : mainClass)
.setPriority(manifestMainAttributes.getValue(PRIORITY_ATTRIBUTE))
.setWebContextPath(manifestMainAttributes.getValue(WEB_CONTEXT_PATH))
Expand All @@ -99,22 +143,49 @@ public Biz createBiz(BizArchive bizArchive) throws IOException {
getInjectDependencies(manifestMainAttributes.getValue(INJECT_PLUGIN_DEPENDENCIES)))
.setInjectExportPackages(manifestMainAttributes.getValue(INJECT_EXPORT_PACKAGES))
.setDeclaredLibraries(manifestMainAttributes.getValue(DECLARED_LIBRARIES))
.setClassPath(bizArchive.getUrls()).setPluginClassPath(getPluginURLs());
.setClassPath(getMergedBizClassPath(bizArchive.getUrls(), bizConfig.getExtensionUrls()));

if (!(bizArchive instanceof DirectoryBizArchive)) {
bizModel.setBizUrl(bizArchive.getUrl());
// prepare dependent plugins and plugin export map
List<String> dependentPlugins = bizConfig.getDependentPlugins();
if (dependentPlugins == null || dependentPlugins.isEmpty()) {
dependentPlugins = StringUtils.strToList(
manifestMainAttributes.getValue("dependent-plugins"),
Constants.MANIFEST_VALUE_SPLIT);
}
resolveExportMapIfNecessary(bizModel, dependentPlugins);

// must be after prepare dependent plugins
bizModel.setPluginClassPath(getPluginURLs(bizModel));

// create biz classloader
BizClassLoader bizClassLoader = new BizClassLoader(bizModel.getIdentity(),
getBizUcp(bizModel.getClassPath()), bizArchive instanceof ExplodedBizArchive
|| bizArchive instanceof DirectoryBizArchive);
getBizUcp(bizModel), bizArchive instanceof ExplodedBizArchive
|| bizArchive instanceof DirectoryBizArchive);
bizClassLoader.setBizModel(bizModel);
bizModel.setClassLoader(bizClassLoader);

// set biz work dir
if (bizModel.getBizUrl() != null) {
bizModel.setBizTempWorkDir(new File(bizModel.getBizUrl().getFile()));
}

return bizModel;
}

@Override
public Biz createBiz(File file) throws IOException {
public Biz createEmbedMasterBiz(ClassLoader masterClassLoader) {
BizModel bizModel = new BizModel();
bizModel.setBizState(BizState.RESOLVED, StateChangeReason.CREATED)
.setBizName(ArkConfigs.getStringValue(MASTER_BIZ)).setBizVersion("1.0.0")
.setMainClass("embed main").setPriority("100").setWebContextPath("/")
.setDenyImportPackages(null).setDenyImportClasses(null).setDenyImportResources(null)
.setInjectPluginDependencies(new HashSet<>()).setInjectExportPackages(null)
.setClassPath(ClassLoaderUtils.getURLs(masterClassLoader))
.setClassLoader(masterClassLoader);
return bizModel;
}

private BizArchive prepareBizArchive(File file) throws IOException {
BizArchive bizArchive;
if (ArkConfigs.isEmbedEnable()) {
File unpackFile = FileUtils.file(file.getAbsolutePath() + "-unpack");
Expand All @@ -131,35 +202,56 @@ public Biz createBiz(File file) throws IOException {
JarFileArchive jarFileArchive = new JarFileArchive(bizFile);
bizArchive = new JarBizArchive(jarFileArchive);
}
BizModel biz = (BizModel) createBiz(bizArchive);
biz.setBizTempWorkDir(file);
yuanyuancin marked this conversation as resolved.
Show resolved Hide resolved
return biz;
return bizArchive;
}

@Override
public Biz createBiz(BizOperation bizOperation, File file) throws IOException {
BizModel biz = (BizModel) createBiz(file);
if (bizOperation != null && !StringUtils.isEmpty(bizOperation.getBizVersion())) {
biz.setBizVersion(bizOperation.getBizVersion());
if (biz.getBizClassLoader() instanceof BizClassLoader) {
BizClassLoader bizClassLoader = (BizClassLoader) (biz.getBizClassLoader());
bizClassLoader.setBizIdentity(biz.getIdentity());
}
private URL[] getMergedBizClassPath(URL[] bizArchiveUrls, URL[] extensionUrls) {
if (extensionUrls == null || extensionUrls.length == 0) {
return bizArchiveUrls;
}
return biz;
return Stream.concat(Arrays.stream(bizArchiveUrls), Arrays.stream(extensionUrls)).toArray(URL[]::new);
}

@Override
public Biz createEmbedMasterBiz(ClassLoader masterClassLoader) {
BizModel bizModel = new BizModel();
bizModel.setBizState(BizState.RESOLVED, StateChangeReason.CREATED)
.setBizName(ArkConfigs.getStringValue(MASTER_BIZ)).setBizVersion("1.0.0")
.setMainClass("embed main").setPriority("100").setWebContextPath("/")
.setDenyImportPackages(null).setDenyImportClasses(null).setDenyImportResources(null)
.setInjectPluginDependencies(new HashSet<>()).setInjectExportPackages(null)
.setClassPath(ClassLoaderUtils.getURLs(masterClassLoader))
.setClassLoader(masterClassLoader);
return bizModel;
private void resolveExportMapIfNecessary(BizModel bizModel, List<String> dependentPlugins) {
Set<Plugin> plugins = new HashSet<>();
if (ArkConfigs.isBizSpecifyDependentPluginsEnable()) {
if (dependentPlugins != null && !dependentPlugins.isEmpty()) {
for (String pluginName : dependentPlugins) {
Plugin plugin = pluginManagerService.getPluginByName(pluginName);
plugins.add(plugin);
Comment on lines +220 to +221
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential NullPointerException when adding plugins

The method getPluginByName(pluginName) may return null if no plugin with the specified name is found. Adding a null plugin to the plugins set can lead to NullPointerException or unexpected behavior later on.

Please add a null check before adding the plugin to the set. For example:

Plugin plugin = pluginManagerService.getPluginByName(pluginName);
if (plugin != null) {
    plugins.add(plugin);
} else {
    // Handle the case where the plugin is not found
}

}
}
} else {
plugins.addAll(pluginManagerService.getPluginsInOrder());
}

bizModel.setDependentPlugins(plugins);
for (Plugin plugin : plugins) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

所以每个 plugin,是需要定义自己的 export 内容的,这部分需要在使用文档里说明。

for (String exportIndex : plugin.getExportPackageNodes()) {
bizModel.getExportNodeAndClassLoaderMap().putIfAbsent(exportIndex, plugin);
}
for (String exportIndex : plugin.getExportPackageStems()) {
bizModel.getExportStemAndClassLoaderMap().putIfAbsent(exportIndex, plugin);
}
for (String exportIndex : plugin.getExportClasses()) {
bizModel.getExportClassAndClassLoaderMap().putIfAbsent(exportIndex, plugin);
}
for (String resource : plugin.getExportResources()) {
bizModel.getExportResourceAndClassLoaderMap().putIfAbsent(resource,
new LinkedList<>());
bizModel.getExportResourceAndClassLoaderMap().get(resource).add(plugin);
}
for (String resource : plugin.getExportPrefixResourceStems()) {
bizModel.getExportPrefixStemResourceAndClassLoaderMap().putIfAbsent(resource,
new LinkedList<>());
bizModel.getExportPrefixStemResourceAndClassLoaderMap().get(resource).add(plugin);
}
for (String resource : plugin.getExportSuffixResourceStems()) {
bizModel.getExportSuffixStemResourceAndClassLoaderMap().putIfAbsent(resource,
new LinkedList<>());
bizModel.getExportSuffixStemResourceAndClassLoaderMap().get(resource).add(plugin);
}
}
}

private Set<String> getInjectDependencies(String injectPluginDependencies) {
Expand All @@ -183,16 +275,16 @@ public boolean matches(Archive.Entry entry) {
});
}

private URL[] getBizUcp(URL[] bizClassPath) {
private URL[] getBizUcp(BizModel bizModel) {
List<URL> bizUcp = new ArrayList<>();
bizUcp.addAll(Arrays.asList(bizClassPath));
bizUcp.addAll(Arrays.asList(getPluginURLs()));
bizUcp.addAll(Arrays.asList(bizModel.getClassPath()));
bizUcp.addAll(Arrays.asList(getPluginURLs(bizModel)));
return bizUcp.toArray(new URL[bizUcp.size()]);
}

private URL[] getPluginURLs() {
private URL[] getPluginURLs(BizModel bizModel) {
List<URL> pluginUrls = new ArrayList<>();
for (Plugin plugin : pluginManagerService.getPluginsInOrder()) {
for (Plugin plugin : bizModel.getDependentPlugins()) {
pluginUrls.add(plugin.getPluginURL());
}
return pluginUrls.toArray(new URL[pluginUrls.size()]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,13 @@ private byte[] getClassBytesFromJar(String jarFilePath, String className) throws

private Class<?> doResolveExportClass(String name) {
if (shouldFindExportedClass(name)) {
ClassLoader importClassLoader = classloaderService.findExportClassLoader(name);
ClassLoader importClassLoader = null;
if (this instanceof BizClassLoader) {
importClassLoader = classloaderService.findExportClassLoaderByBiz(
((BizClassLoader) this).getBizModel(), name);
} else if (this instanceof PluginClassLoader) {
importClassLoader = classloaderService.findExportClassLoader(name);
yuanyuancin marked this conversation as resolved.
Show resolved Hide resolved
}
if (importClassLoader != null) {
try {
Class<?> clazz = importClassLoader.loadClass(name);
Expand Down Expand Up @@ -555,12 +561,18 @@ protected Class<?> resolveJavaAgentClass(String name) {
*/
protected URL getExportResource(String resourceName) {
if (shouldFindExportedResource(resourceName)) {
URL url;
List<ClassLoader> exportResourceClassLoadersInOrder = classloaderService
.findExportResourceClassLoadersInOrder(resourceName);
List<ClassLoader> exportResourceClassLoadersInOrder = null;
if (this instanceof BizClassLoader) {
exportResourceClassLoadersInOrder = classloaderService
.findExportResourceClassLoadersInOrderByBiz(
((BizClassLoader) this).getBizModel(), resourceName);
} else if (this instanceof PluginClassLoader) {
exportResourceClassLoadersInOrder = classloaderService
.findExportResourceClassLoadersInOrder(resourceName);
}
if (exportResourceClassLoadersInOrder != null) {
for (ClassLoader exportResourceClassLoader : exportResourceClassLoadersInOrder) {
url = exportResourceClassLoader.getResource(resourceName);
URL url = exportResourceClassLoader.getResource(resourceName);
if (url != null && this instanceof BizClassLoader) {
if (((BizClassLoader) (this)).getBizModel().isDeclared(url, resourceName)) {
return url;
Expand Down Expand Up @@ -629,8 +641,15 @@ private String transformClassName(String name) {
@SuppressWarnings("unchecked")
protected Enumeration<URL> getExportResources(String resourceName) throws IOException {
if (shouldFindExportedResource(resourceName)) {
List<ClassLoader> exportResourceClassLoadersInOrder = classloaderService
.findExportResourceClassLoadersInOrder(resourceName);
List<ClassLoader> exportResourceClassLoadersInOrder = null;
if (this instanceof BizClassLoader) {
exportResourceClassLoadersInOrder = classloaderService
.findExportResourceClassLoadersInOrderByBiz(
((BizClassLoader) this).getBizModel(), resourceName);
} else if (this instanceof PluginClassLoader) {
exportResourceClassLoadersInOrder = classloaderService
.findExportResourceClassLoadersInOrder(resourceName);
}
if (exportResourceClassLoadersInOrder != null) {
List<Enumeration<URL>> enumerationList = new ArrayList<>();
for (ClassLoader exportResourceClassLoader : exportResourceClassLoadersInOrder) {
Expand Down
Loading
Loading