Browse Source

Merge pull request #3 from gitblit/stop_plugin

Add support for starting, stopping, and unloading a plugin
pull/4/head
Decebal Suiu 11 years ago
parent
commit
eef2ee1cba
  1. 6
      pf4j/pom.xml
  2. 192
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java
  3. 34
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java
  4. 20
      pf4j/src/main/java/ro/fortsoft/pf4j/util/CompoundClassLoader.java

6
pf4j/pom.xml

@ -32,6 +32,12 @@
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
<version>1.6.4</version> <version>1.6.4</version>
</dependency> </dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

192
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java

@ -1,11 +1,11 @@
/* /*
* Copyright 2012 Decebal Suiu * Copyright 2012 Decebal Suiu
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with
* the License. You may obtain a copy of the License in the LICENSE file, or at: * the License. You may obtain a copy of the License in the LICENSE file, or at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
@ -19,6 +19,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -42,7 +43,7 @@ import ro.fortsoft.pf4j.util.ZipFileFilter;
public class DefaultPluginManager implements PluginManager { public class DefaultPluginManager implements PluginManager {
private static final Logger log = LoggerFactory.getLogger(DefaultPluginManager.class); private static final Logger log = LoggerFactory.getLogger(DefaultPluginManager.class);
public static final String DEFAULT_PLUGINS_DIRECTORY = "plugins"; public static final String DEFAULT_PLUGINS_DIRECTORY = "plugins";
public static final String DEVELOPMENT_PLUGINS_DIRECTORY = "../plugins"; public static final String DEVELOPMENT_PLUGINS_DIRECTORY = "../plugins";
@ -52,11 +53,11 @@ public class DefaultPluginManager implements PluginManager {
private File pluginsDirectory; private File pluginsDirectory;
private ExtensionFinder extensionFinder; private ExtensionFinder extensionFinder;
private PluginDescriptorFinder pluginDescriptorFinder; private PluginDescriptorFinder pluginDescriptorFinder;
private PluginClasspath pluginClasspath; private PluginClasspath pluginClasspath;
/** /**
* A map of plugins this manager is responsible for (the key is the 'pluginId'). * A map of plugins this manager is responsible for (the key is the 'pluginId').
*/ */
@ -81,35 +82,35 @@ public class DefaultPluginManager implements PluginManager {
* A list with resolved plugins (resolved dependency). * A list with resolved plugins (resolved dependency).
*/ */
private List<PluginWrapper> resolvedPlugins; private List<PluginWrapper> resolvedPlugins;
/** /**
* A list with started plugins. * A list with started plugins.
*/ */
private List<PluginWrapper> startedPlugins; private List<PluginWrapper> startedPlugins;
private List<String> enabledPlugins; private List<String> enabledPlugins;
private List<String> disabledPlugins; private List<String> disabledPlugins;
/** /**
* A compound class loader of resolved plugins. * A compound class loader of resolved plugins.
*/ */
protected CompoundClassLoader compoundClassLoader; protected CompoundClassLoader compoundClassLoader;
/** /**
* Cache value for the runtime mode. No need to re-read it because it wont change at * Cache value for the runtime mode. No need to re-read it because it wont change at
* runtime. * runtime.
*/ */
private RuntimeMode runtimeMode; private RuntimeMode runtimeMode;
/** /**
* The plugins directory is supplied by System.getProperty("pf4j.pluginsDir", "plugins"). * The plugins directory is supplied by System.getProperty("pf4j.pluginsDir", "plugins").
*/ */
public DefaultPluginManager() { public DefaultPluginManager() {
this.pluginsDirectory = createPluginsDirectory(); this.pluginsDirectory = createPluginsDirectory();
initialize(); initialize();
} }
/** /**
* Constructs DefaultPluginManager which the given plugins directory. * Constructs DefaultPluginManager which the given plugins directory.
* *
@ -118,7 +119,7 @@ public class DefaultPluginManager implements PluginManager {
*/ */
public DefaultPluginManager(File pluginsDirectory) { public DefaultPluginManager(File pluginsDirectory) {
this.pluginsDirectory = pluginsDirectory; this.pluginsDirectory = pluginsDirectory;
initialize(); initialize();
} }
@ -145,7 +146,7 @@ public class DefaultPluginManager implements PluginManager {
public List<PluginWrapper> getStartedPlugins() { public List<PluginWrapper> getStartedPlugins() {
return startedPlugins; return startedPlugins;
} }
/** /**
* Start all active plugins. * Start all active plugins.
*/ */
@ -163,6 +164,34 @@ public class DefaultPluginManager implements PluginManager {
} }
} }
/**
* Start the specified plugin and it's dependencies.
*/
@Override
public PluginState startPlugin(String pluginId) {
if (!plugins.containsKey(pluginId)) {
throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
}
PluginWrapper pluginWrapper = plugins.get(pluginId);
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
if (pluginWrapper.getPluginState().equals(PluginState.STARTED)) {
log.debug("Already started plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return PluginState.STARTED;
}
for (PluginDependency dependency : pluginDescriptor.getDependencies()) {
startPlugin(dependency.getPluginId());
}
try {
log.info("Start plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
pluginWrapper.getPlugin().start();
pluginWrapper.setPluginState(PluginState.STARTED);
startedPlugins.add(pluginWrapper);
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
return pluginWrapper.getPluginState();
}
/** /**
* Stop all active plugins. * Stop all active plugins.
*/ */
@ -170,17 +199,49 @@ public class DefaultPluginManager implements PluginManager {
public void stopPlugins() { public void stopPlugins() {
// stop started plugins in reverse order // stop started plugins in reverse order
Collections.reverse(startedPlugins); Collections.reverse(startedPlugins);
for (PluginWrapper pluginWrapper : startedPlugins) { Iterator<PluginWrapper> itr = startedPlugins.iterator();
while (itr.hasNext()) {
PluginWrapper pluginWrapper = itr.next();
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
try { try {
log.info("Stop plugin '{}'", pluginWrapper.getDescriptor().getPluginId()); log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
pluginWrapper.getPlugin().stop(); pluginWrapper.getPlugin().stop();
pluginWrapper.setPluginState(PluginState.STOPPED); pluginWrapper.setPluginState(PluginState.STOPPED);
itr.remove();
} catch (PluginException e) { } catch (PluginException e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
} }
} }
} }
/**
* Stop the specified plugin and it's dependencies.
*/
@Override
public PluginState stopPlugin(String pluginId) {
if (!plugins.containsKey(pluginId)) {
throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
}
PluginWrapper pluginWrapper = plugins.get(pluginId);
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
if (pluginWrapper.getPluginState().equals(PluginState.STOPPED)) {
log.debug("Already stopped plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return PluginState.STOPPED;
}
for (PluginDependency dependency : pluginDescriptor.getDependencies()) {
stopPlugin(dependency.getPluginId());
}
try {
log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
pluginWrapper.getPlugin().stop();
pluginWrapper.setPluginState(PluginState.STOPPED);
startedPlugins.remove(pluginWrapper);
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
return pluginWrapper.getPluginState();
}
/** /**
* Load plugins. * Load plugins.
*/ */
@ -233,6 +294,45 @@ public class DefaultPluginManager implements PluginManager {
} }
} }
@Override
public boolean unloadPlugin(String pluginId) {
try {
PluginState state = stopPlugin(pluginId);
if (!PluginState.STOPPED.equals(state)) {
return false;
}
PluginWrapper pluginWrapper = plugins.get(pluginId);
PluginDescriptor descriptor = pluginWrapper.getDescriptor();
List<PluginDependency> dependencies = descriptor.getDependencies();
for (PluginDependency dependency : dependencies) {
if (!unloadPlugin(dependency.getPluginId())) {
return false;
}
}
// remove the plugin
plugins.remove(pluginId);
resolvedPlugins.remove(pluginWrapper);
pathToIdMap.remove(pluginWrapper.getPluginPath());
// remove the classloader
if (pluginClassLoaders.containsKey(pluginId)) {
PluginClassLoader classLoader = pluginClassLoaders.remove(pluginId);
compoundClassLoader.removeLoader(classLoader);
try {
classLoader.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
return true;
} catch (IllegalArgumentException e) {
// ignore not found exceptions because this method is recursive
}
return false;
}
/** /**
* Get plugin class loader for this path. * Get plugin class loader for this path.
*/ */
@ -248,24 +348,24 @@ public class DefaultPluginManager implements PluginManager {
for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) { for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
extensions.add(extensionWrapper.getInstance()); extensions.add(extensionWrapper.getInstance());
} }
return extensions; return extensions;
} }
@Override @Override
public RuntimeMode getRuntimeMode() { public RuntimeMode getRuntimeMode() {
if (runtimeMode == null) { if (runtimeMode == null) {
// retrieves the runtime mode from system // retrieves the runtime mode from system
String modeAsString = System.getProperty("pf4j.mode", RuntimeMode.DEPLOYMENT.toString()); String modeAsString = System.getProperty("pf4j.mode", RuntimeMode.DEPLOYMENT.toString());
runtimeMode = RuntimeMode.byName(modeAsString); runtimeMode = RuntimeMode.byName(modeAsString);
log.info("PF4J runtime mode is '{}'", runtimeMode); log.info("PF4J runtime mode is '{}'", runtimeMode);
} }
return runtimeMode; return runtimeMode;
} }
/** /**
* Retrieves the {@link PluginWrapper} that loaded the given class 'clazz'. * Retrieves the {@link PluginWrapper} that loaded the given class 'clazz'.
*/ */
@ -276,63 +376,63 @@ public class DefaultPluginManager implements PluginManager {
return plugin; return plugin;
} }
} }
return null; return null;
} }
/** /**
* Add the possibility to override the PluginDescriptorFinder. * Add the possibility to override the PluginDescriptorFinder.
* By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a * By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
* PropertiesPluginDescriptorFinder is returned else this method returns * PropertiesPluginDescriptorFinder is returned else this method returns
* DefaultPluginDescriptorFinder. * DefaultPluginDescriptorFinder.
*/ */
protected PluginDescriptorFinder createPluginDescriptorFinder() { protected PluginDescriptorFinder createPluginDescriptorFinder() {
if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) { if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
return new PropertiesPluginDescriptorFinder(); return new PropertiesPluginDescriptorFinder();
} }
return new DefaultPluginDescriptorFinder(pluginClasspath); return new DefaultPluginDescriptorFinder(pluginClasspath);
} }
/** /**
* Add the possibility to override the ExtensionFinder. * Add the possibility to override the ExtensionFinder.
*/ */
protected ExtensionFinder createExtensionFinder() { protected ExtensionFinder createExtensionFinder() {
return new DefaultExtensionFinder(compoundClassLoader); return new DefaultExtensionFinder(compoundClassLoader);
} }
/** /**
* Add the possibility to override the PluginClassPath. * Add the possibility to override the PluginClassPath.
* By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a * By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
* DevelopmentPluginClasspath is returned else this method returns * DevelopmentPluginClasspath is returned else this method returns
* PluginClasspath. * PluginClasspath.
*/ */
protected PluginClasspath createPluginClasspath() { protected PluginClasspath createPluginClasspath() {
if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) { if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
return new DevelopmentPluginClasspath(); return new DevelopmentPluginClasspath();
} }
return new PluginClasspath(); return new PluginClasspath();
} }
protected boolean isPluginDisabled(String pluginId) { protected boolean isPluginDisabled(String pluginId) {
if (enabledPlugins.isEmpty()) { if (enabledPlugins.isEmpty()) {
return disabledPlugins.contains(pluginId); return disabledPlugins.contains(pluginId);
} }
return !enabledPlugins.contains(pluginId); return !enabledPlugins.contains(pluginId);
} }
protected FileFilter createHiddenPluginFilter() { protected FileFilter createHiddenPluginFilter() {
return new HiddenFilter(); return new HiddenFilter();
} }
/** /**
* Add the possibility to override the plugins directory. * Add the possibility to override the plugins directory.
* If a "pf4j.pluginsDir" system property is defined than this method returns * If a "pf4j.pluginsDir" system property is defined than this method returns
* that directory. * that directory.
* If getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a * If getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
* DEVELOPMENT_PLUGINS_DIRECTORY ("../plugins") is returned else this method returns * DEVELOPMENT_PLUGINS_DIRECTORY ("../plugins") is returned else this method returns
* DEFAULT_PLUGINS_DIRECTORY ("plugins"). * DEFAULT_PLUGINS_DIRECTORY ("plugins").
* @return * @return
*/ */
@ -345,10 +445,10 @@ public class DefaultPluginManager implements PluginManager {
pluginsDir = DEFAULT_PLUGINS_DIRECTORY; pluginsDir = DEFAULT_PLUGINS_DIRECTORY;
} }
} }
return new File(pluginsDir); return new File(pluginsDir);
} }
private void initialize() { private void initialize() {
plugins = new HashMap<String, PluginWrapper>(); plugins = new HashMap<String, PluginWrapper>();
pluginClassLoaders = new HashMap<String, PluginClassLoader>(); pluginClassLoaders = new HashMap<String, PluginClassLoader>();
@ -358,7 +458,7 @@ public class DefaultPluginManager implements PluginManager {
startedPlugins = new ArrayList<PluginWrapper>(); startedPlugins = new ArrayList<PluginWrapper>();
disabledPlugins = new ArrayList<String>(); disabledPlugins = new ArrayList<String>();
compoundClassLoader = new CompoundClassLoader(); compoundClassLoader = new CompoundClassLoader();
pluginClasspath = createPluginClasspath(); pluginClasspath = createPluginClasspath();
pluginDescriptorFinder = createPluginDescriptorFinder(); pluginDescriptorFinder = createPluginDescriptorFinder();
extensionFinder = createExtensionFinder(); extensionFinder = createExtensionFinder();
@ -367,7 +467,7 @@ public class DefaultPluginManager implements PluginManager {
// create a list with plugin identifiers that should be only accepted by this manager (whitelist from plugins/enabled.txt file) // create a list with plugin identifiers that should be only accepted by this manager (whitelist from plugins/enabled.txt file)
enabledPlugins = FileUtils.readLines(new File(pluginsDirectory, "enabled.txt"), true); enabledPlugins = FileUtils.readLines(new File(pluginsDirectory, "enabled.txt"), true);
log.info("Enabled plugins: {}", enabledPlugins); log.info("Enabled plugins: {}", enabledPlugins);
// create a list with plugin identifiers that should not be accepted by this manager (blacklist from plugins/disabled.txt file) // create a list with plugin identifiers that should not be accepted by this manager (blacklist from plugins/disabled.txt file)
disabledPlugins = FileUtils.readLines(new File(pluginsDirectory, "disabled.txt"), true); disabledPlugins = FileUtils.readLines(new File(pluginsDirectory, "disabled.txt"), true);
log.info("Disabled plugins: {}", disabledPlugins); log.info("Disabled plugins: {}", disabledPlugins);
@ -406,7 +506,7 @@ public class DefaultPluginManager implements PluginManager {
PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory, pluginClasspath); PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory, pluginClasspath);
pluginLoader.load(); pluginLoader.load();
log.debug("Loaded plugin '{}'", pluginPath); log.debug("Loaded plugin '{}'", pluginPath);
// create the plugin wrapper // create the plugin wrapper
log.debug("Creating wrapper for plugin '{}'", pluginPath); log.debug("Creating wrapper for plugin '{}'", pluginPath);
PluginWrapper pluginWrapper = new PluginWrapper(pluginDescriptor, pluginPath, pluginLoader.getPluginClassLoader()); PluginWrapper pluginWrapper = new PluginWrapper(pluginDescriptor, pluginPath, pluginLoader.getPluginClassLoader());
@ -456,5 +556,5 @@ public class DefaultPluginManager implements PluginManager {
log.info("Plugin '{}' resolved", pluginWrapper.getDescriptor().getPluginId()); log.info("Plugin '{}' resolved", pluginWrapper.getDescriptor().getPluginId());
} }
} }
} }

34
pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java

@ -1,11 +1,11 @@
/* /*
* Copyright 2012 Decebal Suiu * Copyright 2012 Decebal Suiu
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with
* the License. You may obtain a copy of the License in the LICENSE file, or at: * the License. You may obtain a copy of the License in the LICENSE file, or at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
@ -36,7 +36,7 @@ public interface PluginManager {
* Retrieves all unresolved plugins (with unresolved dependency). * Retrieves all unresolved plugins (with unresolved dependency).
*/ */
public List<PluginWrapper> getUnresolvedPlugins(); public List<PluginWrapper> getUnresolvedPlugins();
/** /**
* Retrieves all started plugins. * Retrieves all started plugins.
*/ */
@ -52,18 +52,40 @@ public interface PluginManager {
*/ */
public void startPlugins(); public void startPlugins();
/**
* Start the specified plugin and it's dependencies.
*
* @return the plugin state
*/
public PluginState startPlugin(String pluginId);
/** /**
* Stop all active plugins. * Stop all active plugins.
*/ */
public void stopPlugins(); public void stopPlugins();
/**
* Stop the specified plugin and it's dependencies.
*
* @return the plugin state
*/
public PluginState stopPlugin(String pluginId);
/**
* Unload a plugin.
*
* @param pluginId
* @return true if the plugin was unloaded
*/
public boolean unloadPlugin(String pluginId);
public PluginClassLoader getPluginClassLoader(String pluginId); public PluginClassLoader getPluginClassLoader(String pluginId);
public <T> List<T> getExtensions(Class<T> type); public <T> List<T> getExtensions(Class<T> type);
/** /**
* The runtime mode. Must currently be either DEVELOPMENT or DEPLOYMENT. * The runtime mode. Must currently be either DEVELOPMENT or DEPLOYMENT.
*/ */
public RuntimeMode getRuntimeMode(); public RuntimeMode getRuntimeMode();
} }

20
pf4j/src/main/java/ro/fortsoft/pf4j/util/CompoundClassLoader.java

@ -1,11 +1,11 @@
/* /*
* Copyright 2012 Decebal Suiu * Copyright 2012 Decebal Suiu
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with
* the License. You may obtain a copy of the License in the LICENSE file, or at: * the License. You may obtain a copy of the License in the LICENSE file, or at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
@ -23,7 +23,7 @@ import java.util.Set;
/** /**
* A class loader that has multiple loaders and uses them for loading classes and resources. * A class loader that has multiple loaders and uses them for loading classes and resources.
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class CompoundClassLoader extends ClassLoader { public class CompoundClassLoader extends ClassLoader {
@ -34,6 +34,10 @@ public class CompoundClassLoader extends ClassLoader {
loaders.add(loader); loaders.add(loader);
} }
public void removeLoader(ClassLoader loader) {
loaders.remove(loader);
}
@Override @Override
public Class<?> findClass(String name) throws ClassNotFoundException { public Class<?> findClass(String name) throws ClassNotFoundException {
for (ClassLoader loader : loaders) { for (ClassLoader loader : loaders) {
@ -43,7 +47,7 @@ public class CompoundClassLoader extends ClassLoader {
// try next // try next
} }
} }
throw new ClassNotFoundException(name); throw new ClassNotFoundException(name);
} }
@ -55,7 +59,7 @@ public class CompoundClassLoader extends ClassLoader {
return url; return url;
} }
} }
return null; return null;
} }
@ -65,8 +69,8 @@ public class CompoundClassLoader extends ClassLoader {
for (ClassLoader loader : loaders) { for (ClassLoader loader : loaders) {
resources.addAll(Collections.list(loader.getResources(name))); resources.addAll(Collections.list(loader.getResources(name)));
} }
return Collections.enumeration(resources); return Collections.enumeration(resources);
} }
} }

Loading…
Cancel
Save