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 9 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 @@ -615,4 +628,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 @@ -48,9 +48,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,6 +82,11 @@ public class BizFactoryServiceImpl implements BizFactoryService {

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

@Override
public Biz createBiz(BizArchive bizArchive, URL[] extensionUrls) throws IOException {
AssertUtils.isTrue(isArkBiz(bizArchive), "Archive must be a ark biz!");
BizModel bizModel = new BizModel();
Attributes manifestMainAttributes = bizArchive.getManifest().getMainAttributes();
Expand All @@ -99,7 +106,10 @@ 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(), extensionUrls)).setPluginClassPath(getPluginURLs());

// prepare dependent plugins and plugin export map
resolveExportMapIfNecessary(bizModel, manifestMainAttributes.getValue("dependent-plugins"));

if (!(bizArchive instanceof DirectoryBizArchive)) {
bizModel.setBizUrl(bizArchive.getUrl());
Expand All @@ -113,8 +123,63 @@ public Biz createBiz(BizArchive bizArchive) throws IOException {
return bizModel;
}

private URL[] getMergedBizClassPath(URL[] bizArchiveUrls, URL[] extensionUrls) {
if (extensionUrls == null || extensionUrls.length == 0) {
return bizArchiveUrls;
}
return Stream.concat(Arrays.stream(bizArchiveUrls), Arrays.stream(extensionUrls)).toArray(URL[]::new);
}

private void resolveExportMapIfNecessary(BizModel bizModel, String dependentPlugins) {
Set<Plugin> plugins = new HashSet<>();
if (ArkConfigs.areAllPluginsVisibleForBiz()) {
plugins.addAll(pluginManagerService.getPluginsInOrder());
}

if (dependentPlugins != null) {
Set<String> pluginNames = StringUtils.strToSet(dependentPlugins, Constants.MANIFEST_VALUE_SPLIT);
for (String pluginName : pluginNames) {
Plugin plugin = pluginManagerService.getPluginByName(pluginName);
plugins.add(plugin);
}
}

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);
}
}
}

@Override
public Biz createBiz(File file) throws IOException {
return createBiz(file, null);
}

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

@Override
public Biz createBiz(BizOperation bizOperation, File file) throws IOException {
BizModel biz = (BizModel) createBiz(file);
BizModel biz = (BizModel) createBiz(file, null);
if (bizOperation != null && !StringUtils.isEmpty(bizOperation.getBizVersion())) {
biz.setBizVersion(bizOperation.getBizVersion());
if (biz.getBizClassLoader() instanceof BizClassLoader) {
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,15 @@ 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 +638,12 @@ 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
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.alipay.sofa.ark.common.util.AssertUtils;
import com.alipay.sofa.ark.common.util.ClassLoaderUtils;
import com.alipay.sofa.ark.common.util.ClassUtils;
import com.alipay.sofa.ark.container.model.BizModel;
import com.alipay.sofa.ark.container.model.PluginModel;
import com.alipay.sofa.ark.exception.ArkRuntimeException;
import com.alipay.sofa.ark.spi.constant.Constants;
Expand All @@ -39,6 +40,7 @@
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -188,6 +190,25 @@ public ClassLoader findExportClassLoader(String className) {
}
}

@Override
public ClassLoader findExportClassLoaderByBiz(Biz biz, String className) {
BizModel bizModel = (BizModel) biz;
Plugin plugin = bizModel.getExportClassAndClassLoaderMap().get(className);
String packageName = ClassUtils.getPackageName(className);
if (plugin == null) {
plugin = bizModel.getExportNodeAndClassLoaderMap().get(packageName);
}
while (!Constants.DEFAULT_PACKAGE.equals(packageName) && plugin == null) {
plugin = bizModel.getExportStemAndClassLoaderMap().get(packageName);
packageName = ClassUtils.getPackageName(packageName);
}
if (plugin != null) {
return plugin.getPluginClassLoader();
} else {
return null;
}
}

@Override
public Plugin findExportPlugin(String className) {
Plugin plugin = exportClassAndClassLoaderMap.get(className);
Expand Down Expand Up @@ -239,6 +260,38 @@ public List<ClassLoader> findExportResourceClassLoadersInOrder(String resourceNa
}
}

@Override
public List<ClassLoader> findExportResourceClassLoadersInOrderByBiz(Biz biz, String resourceName) {
BizModel bizModel = (BizModel) biz;
List<Plugin> plugins = findExportResourcePluginsInOrderByBiz(bizModel, resourceName);

if (plugins != null) {
return plugins.stream().map(Plugin::getPluginClassLoader).collect(Collectors.toList());
} else {
return null;
}
}

private List<Plugin> findExportResourcePluginsInOrderByBiz(BizModel bizModel,
String resourceName) {
if (bizModel.getExportResourceAndClassLoaderMap().containsKey(resourceName)) {
return bizModel.getExportResourceAndClassLoaderMap().get(resourceName);
}

for (String stemResource : bizModel.getExportPrefixStemResourceAndClassLoaderMap().keySet()) {
if (resourceName.startsWith(stemResource)) {
return bizModel.getExportPrefixStemResourceAndClassLoaderMap().get(stemResource);
}
}

for (String stemResource : bizModel.getExportSuffixStemResourceAndClassLoaderMap().keySet()) {
if (resourceName.endsWith(stemResource)) {
return bizModel.getExportSuffixStemResourceAndClassLoaderMap().get(stemResource);
}
}
return null;
}

private List<Plugin> findExportResourcePluginsInOrder(String resourceName) {
if (exportResourceAndClassLoaderMap.containsKey(resourceName)) {
return exportResourceAndClassLoaderMap.get(resourceName);
Expand Down
Loading
Loading