Author: bombe
Date: 2007-10-28 18:44:08 +0000 (Sun, 28 Oct 2007)
New Revision: 15624
Added:
trunk/freenet/src/freenet/support/JarClassLoader.java
trunk/freenet/src/freenet/support/io/Closer.java
trunk/freenet/src/freenet/support/io/StreamCopier.java
Modified:
trunk/freenet/src/freenet/clients/http/PproxyToadlet.java
trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties
trunk/freenet/src/freenet/node/TextModeClientInterface.java
trunk/freenet/src/freenet/pluginmanager/PluginHandler.java
trunk/freenet/src/freenet/pluginmanager/PluginInfoWrapper.java
trunk/freenet/src/freenet/pluginmanager/PluginManager.java
Log:
fix #1815, start plugins in background thread
improve plugin loading interface
save downloaded plugins
Modified: trunk/freenet/src/freenet/clients/http/PproxyToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/PproxyToadlet.java 2007-10-28
18:33:01 UTC (rev 15623)
+++ trunk/freenet/src/freenet/clients/http/PproxyToadlet.java 2007-10-28
18:44:08 UTC (rev 15624)
@@ -9,6 +9,7 @@
import java.net.URL;
import java.util.Date;
import java.util.Iterator;
+import java.util.Set;
import freenet.client.HighLevelSimpleClient;
import freenet.l10n.L10n;
@@ -21,9 +22,11 @@
import freenet.pluginmanager.PluginInfoWrapper;
import freenet.pluginmanager.PluginManager;
import freenet.pluginmanager.RedirectPluginHTTPException;
+import freenet.pluginmanager.PluginManager.PluginProgress;
import freenet.support.HTMLNode;
import freenet.support.Logger;
import freenet.support.MultiValueTable;
+import freenet.support.TimeUtil;
import freenet.support.api.HTTPRequest;
import freenet.support.io.FileUtil;
@@ -118,104 +121,28 @@
}
else
{
+ final boolean logMINOR = Logger.shouldLog(Logger.MINOR,
this);
+ final boolean logNORMAL =
Logger.shouldLog(Logger.NORMAL, this);
- if (request.isPartSet("load")) {
- String filename =
request.getPartAsString("load", MAX_PLUGIN_NAME_LENGTH);
- final boolean logMINOR =
Logger.shouldLog(Logger.MINOR, this);
- boolean downloaded = false;
-
- if(logMINOR) Logger.minor(this, "Loading
"+filename);
- if (filename.endsWith("#")) {
- for (int tries = 0; (tries <= 5) &&
(downloaded == false); tries++) {
- if (filename.indexOf('@') > -1)
{
- Logger
- .error(this,
- "We don't allow
downloads from anywhere else but our server");
- sendErrorPage(ctx, 403,
l10n("Error"), l10n("downloadNotAllowedFromRemoteServer"));
- return;
- }
- String pluginname =
filename.substring(0,
-
filename.length() - 1);
- filename = null;
-
- URL url;
- InputStream is = null;
-
- try {
- url = new URL(
-
"http://downloads.freenetproject.org/alpha/plugins/"
- +
pluginname + ".jar.url");
- if (logMINOR)
-
Logger.minor(this, "Downloading " + url);
- is = url.openStream();
-
- File pluginsDirectory =
new File("plugins");
- if
(!pluginsDirectory.exists()) {
- Logger
- .normal(this,
- "The plugin
directory hasn't been found, let's create it");
- if
(!pluginsDirectory.mkdir()) {
-
sendErrorPage(ctx, 500, l10n("Error"), l10n("pluginDirectoryNotCreated"));
- return;
- }
- }
-
- File finalFile = new
File("plugins/" + pluginname
- +
".jar");
- if
(!FileUtil.writeTo(is, finalFile))
-
Logger.error(this,
-
"Failed to rename the temporary file into "
-
+ finalFile);
-
- filename = "*@file://"
- +
FileUtil.getCanonicalFile(finalFile);
- if (logMINOR)
-
Logger.minor(this, "Rewritten to " + filename);
- downloaded = true;
- } catch (MalformedURLException
mue) {
- Logger.error(this,
-
"MalformedURLException has occured : " + mue,
- mue);
- sendErrorPage(ctx,
l10n("Error"), l10n("pluginNotDownloaded"), mue);
- return;
- } catch (FileNotFoundException
e) {
- Logger.error(this,
-
"FileNotFoundException has occured : " + e, e);
- sendErrorPage(ctx,
l10n("Error"), l10n("pluginNotDownloaded"), e);
- return;
- } catch (IOException ioe) {
-
System.out.println("Caught :" + ioe.getMessage());
- ioe.printStackTrace();
- sendErrorPage(ctx,
l10n("Error"), l10n("pluginNotDownloaded"), ioe);
- return;
- } finally {
- try {
- if (is != null)
-
is.close();
- } catch (IOException
ioe) {
- }
- }
- }
- if (filename == null) {
- sendErrorPage(ctx, 500,
l10n("Error"), l10n("pluginNotDownloaded"));
- return;
- }
- else if(!downloaded) {
- Logger.error(this, "Can't load
the given plugin; giving up");
- sendErrorPage(ctx, 500,
l10n("Error"), l10n("pluginNotDownloaded"));
- return;
- }
+ if (request.isPartSet("submit-official") ||
request.isPartSet("submit-other")) {
+ String pluginName = null;
+ boolean refresh =
request.isPartSet("refresh-on-startup");
+ if (request.isPartSet("submit-official")) {
+ pluginName =
request.getPartAsString("plugin-name", 40);
+ } else {
+ pluginName =
request.getPartAsString("plugin-url", 200);
}
-
- pm.startPlugin(filename, true);
+ pm.startPlugin(pluginName, refresh, true);
headers.put("Location", ".");
ctx.sendReplyHeaders(302, "Found", headers,
null, 0);
return;
- }if (request.isPartSet("cancel")){
+ }
+ if (request.isPartSet("cancel")){
headers.put("Location", "/plugins/");
ctx.sendReplyHeaders(302, "Found", headers,
null, 0);
return;
- }if (request.getPartAsString("unloadconfirm",
MAX_PLUGIN_NAME_LENGTH).length() > 0) {
+ }
+ if (request.getPartAsString("unloadconfirm",
MAX_PLUGIN_NAME_LENGTH).length() > 0) {
pm.killPlugin(request.getPartAsString("unloadconfirm", MAX_PLUGIN_NAME_LENGTH),
MAX_THREADED_UNLOAD_WAIT_TIME);
HTMLNode pageNode =
ctx.getPageMaker().getPageNode(l10n("plugins"), ctx);
HTMLNode contentNode =
ctx.getPageMaker().getContentNode(pageNode);
@@ -243,11 +170,13 @@
return;
}else if (request.getPartAsString("reload",
MAX_PLUGIN_NAME_LENGTH).length() > 0) {
String fn = null;
+ boolean refresh = false;
Iterator it = pm.getPlugins().iterator();
while (it.hasNext()) {
PluginInfoWrapper pi =
(PluginInfoWrapper) it.next();
if
(pi.getThreadName().equals(request.getPartAsString("reload",
MAX_PLUGIN_NAME_LENGTH))) {
fn = pi.getFilename();
+ refresh = pi.isAutoRefresh();
break;
}
}
@@ -257,7 +186,7 @@
L10n.getString("PluginToadlet.pluginNotFoundReload"));
} else {
pm.killPlugin(request.getPartAsString("reload", MAX_PLUGIN_NAME_LENGTH),
MAX_THREADED_UNLOAD_WAIT_TIME);
- pm.startPlugin(fn, true);
+ pm.startPlugin(fn, refresh, true);
headers.put("Location", ".");
ctx.sendReplyHeaders(302, "Found",
headers, null, 0);
@@ -297,7 +226,18 @@
Logger.minor(this, "Pproxy fetching "+path);
try {
if (path.equals("")) {
- this.showPluginList(ctx, request, pm);
+ if (!ctx.isAllowedFullAccess()) {
+ super.sendErrorPage(ctx, 403,
"Unauthorized", L10n.getString("Toadlet.unauthorized"));
+ return;
+ }
+
+ HTMLNode pageNode =
ctx.getPageMaker().getPageNode(l10n("pluginsWithNodeName", "name",
core.getMyName()), ctx);
+ HTMLNode contentNode =
ctx.getPageMaker().getContentNode(pageNode);
+
+ this.showStartingPlugins(ctx, request, pm,
contentNode);
+ this.showPluginList(ctx, request, pm,
contentNode);
+
+ writeHTMLReply(ctx, 200, "OK",
pageNode.generate());
} else {
// split path into plugin class name and 'data'
path for plugin
int to = path.indexOf("/");
@@ -336,16 +276,44 @@
}
}
- private void showPluginList(ToadletContext ctx, HTTPRequest request,
PluginManager pm) throws ToadletContextClosedException, IOException {
- if(!ctx.isAllowedFullAccess()) {
- super.sendErrorPage(ctx, 403, "Unauthorized",
L10n.getString("Toadlet.unauthorized"));
- return;
+ /**
+ * Shows a list of all currently loading plugins.
+ *
+ * @param toadletContext
+ * The toadlet context
+ * @param request
+ * The HTTP request
+ * @param pluginManager
+ * The plugin manager
+ * @throws ToadletContextClosedException
+ * if the toadlet context is closed
+ * @throws IOException
+ * if an I/O error occurs
+ */
+ private void showStartingPlugins(ToadletContext toadletContext,
HTTPRequest request, PluginManager pluginManager, HTMLNode contentNode) throws
ToadletContextClosedException, IOException {
+ Set/*<PluginProgress>*/ startingPlugins =
pluginManager.getStartingPlugins();
+ if (!startingPlugins.isEmpty()) {
+ HTMLNode startingPluginsBox =
contentNode.addChild("div", "class", "infobox infobox-normal");
+ startingPluginsBox.addChild("div", "class",
"infobox-header", l10n("startingPluginsTitle"));
+ HTMLNode startingPluginsContent =
startingPluginsBox.addChild("div", "class", "infobox-content");
+ HTMLNode startingPluginsTable =
startingPluginsContent.addChild("table");
+ HTMLNode startingPluginsHeader =
startingPluginsTable.addChild("tr");
+ startingPluginsHeader.addChild("th",
l10n("startingPluginName"));
+ startingPluginsHeader.addChild("th",
l10n("startingPluginStatus"));
+ startingPluginsHeader.addChild("th",
l10n("startingPluginTime"));
+ Iterator/*<PluginProgress>*/ startingPluginsIterator =
startingPlugins.iterator();
+ while (startingPluginsIterator.hasNext()) {
+ PluginProgress pluginProgress =
(PluginProgress) startingPluginsIterator.next();
+ HTMLNode startingPluginsRow =
startingPluginsTable.addChild("tr");
+ startingPluginsRow.addChild("td",
pluginProgress.getName());
+ startingPluginsRow.addChild("td",
l10n("startingPluginStatus." + pluginProgress.getProgress().toString()));
+ startingPluginsRow.addChild("td", "aligh",
"right", TimeUtil.formatTime(pluginProgress.getTime()));
+ }
}
+ }
+ private void showPluginList(ToadletContext ctx, HTTPRequest request,
PluginManager pm, HTMLNode contentNode) throws ToadletContextClosedException,
IOException {
if (!request.hasParameters()) {
- HTMLNode pageNode =
ctx.getPageMaker().getPageNode(l10n("pluginsWithNodeName", "name",
core.getMyName()), ctx);
- HTMLNode contentNode =
ctx.getPageMaker().getContentNode(pageNode);
-
HTMLNode infobox = contentNode.addChild("div", "class",
"infobox infobox-normal");
infobox.addChild("div", "class", "infobox-header",
L10n.getString("PluginToadlet.pluginListTitle"));
HTMLNode infoboxContent = infobox.addChild("div",
"class", "infobox-content");
@@ -386,12 +354,27 @@
}
}
- HTMLNode addForm = ctx.addFormChild(infoboxContent,
".", "addPluginForm");
- HTMLNode loadDiv = addForm.addChild("div");
- loadDiv.addChild("#", (l10n("loadPluginLabel") + ' '));
- loadDiv.addChild("input", new String[] { "type",
"name", "size" }, new String[] { "text", "load", "40" });
- loadDiv.addChild("input", new String[] { "type",
"value" }, new String[] { "submit", l10n("Load") });
- writeHTMLReply(ctx, 200, "OK", pageNode.generate());
+ /* box for "official" plugins. */
+ HTMLNode addOfficialPluginBox =
contentNode.addChild("div", "class", "infobox infobox-normal");
+ addOfficialPluginBox.addChild("div", "class",
"infobox-header", l10n("loadOfficialPlugin"));
+ HTMLNode addOfficialPluginContent =
addOfficialPluginBox.addChild("div", "class", "infobox-content");
+ HTMLNode addOfficialForm =
ctx.addFormChild(addOfficialPluginContent, ".", "addOfficialPluginForm");
+ addOfficialForm.addChild("div",
l10n("loadOfficialPluginText"));
+ addOfficialForm.addChild("#",
(l10n("loadOfficialPluginLabel") + ": "));
+ addOfficialForm.addChild("input", new String[] {
"type", "name", "size" }, new String[] { "text", "plugin-name", "40" });
+ addOfficialForm.addChild("input", new String[] {
"type", "name", "value", "checked" }, new String[] { "checkbox",
"refresh-on-startup", "tue", "checked" }, l10n("refreshOnStartup"));
+ addOfficialForm.addChild("input", new String[] {
"type", "name", "value" }, new String[] { "submit", "submit-official",
l10n("Load") });
+
+ /* box for unofficial plugins. */
+ HTMLNode addOtherPluginBox =
contentNode.addChild("div", "class", "infobox infobox-normal");
+ addOtherPluginBox.addChild("div", "class",
"infobox-header", l10n("loadOtherPlugin"));
+ HTMLNode addOtherPluginContent =
addOtherPluginBox.addChild("div", "class", "infobox-content");
+ HTMLNode addOtherForm =
ctx.addFormChild(addOtherPluginContent, ".", "addOtherPluginForm");
+ addOtherForm.addChild("div",
l10n("loadOtherPluginText"));
+ addOtherForm.addChild("#", (l10n("loadOtherURLLabel") +
": "));
+ addOtherForm.addChild("input", new String[] { "type",
"name", "size" }, new String[] { "text", "plugin-url", "80" });
+ addOtherForm.addChild("input", new String[] { "type",
"name", "value" }, new String[] { "checkbox", "refresh-on-startup", "true" },
l10n("refreshOnStartup"));
+ addOtherForm.addChild("input", new String[] { "type",
"name", "value" }, new String[] { "submit", "submit-other", l10n("Load") });
}
}
}
Modified: trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties
===================================================================
--- trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties 2007-10-28
18:33:01 UTC (rev 15623)
+++ trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties 2007-10-28
18:44:08 UTC (rev 15624)
@@ -684,6 +684,9 @@
PluginManager.loadedPluginsLong=A list of plugins that are started when the
node starts
PluginManager.pluginReqNewerJVM=The plugin ${name} seems to require a later
JVM. Please install at least Sun java 1.5, or remove the plugin.
PluginManager.pluginReqNewerJVMTitle=Later JVM required by plugin ${name}.
+PluginManager.pluginLoadingFailedTitle=Could not load plugin!
+PluginManager.pluginLoadingFailed=The plugin specified by ${name} could not be
loaded
+PluginManager.pluginLoadingFailedWithMessage=The plugin specified by ${name}
could not be loaded: ${message}
PluginToadlet.addPluginTitle=Add a plugin
PluginToadlet.failedToLoadPlugin=Failed to load plugin.
PluginToadlet.failedToLoadPluginCheckClass=The plugin you requested could not
be loaded. Please verify the name of the plugin's class and the URL, if you
gave one.
@@ -706,7 +709,12 @@
PproxyToadlet.Error=Error
PproxyToadlet.internalIDTitle=Internal ID
PproxyToadlet.Load=Load
-PproxyToadlet.loadPluginLabel=Load Plugin:
+PproxyToadlet.loadOfficialPlugin=Add an Official Plugin
+PproxyToadlet.loadOfficialPluginText=These plugins are hosted on servers of
The Freenet Project. We believe that these plugins are free of privacy leaks
though we will not guarantee it. Possible plugin names are: Echo, Freemail,
HelloWorld (yes, really), JSTUN, Librarian, MDNSDiscovery, SNMP, TestGallery,
UPnP, XMLLibrarian, XMLSpider. Plugin names are case-sensitive.
+PproxyToadlet.loadOfficialPluginLabel=Load Official Plugin
+PproxyToadlet.loadOtherPlugin=Add an Unofficial Plugin
+PproxyToadlet.loadOtherPluginText=Here you can enter the URL of a plugin you
want to load. Other plugins than the ones listed above are not even remotely
supported or checked for privacy leaks by us, so if you load a remote plugin
here, you are basically on your own.
+PproxyToadlet.loadOtherURLLabel=Plugin URL
PproxyToadlet.noPlugins=No plugins loaded
PproxyToadlet.pluginNotFoundReload=The specified plugin could not be located
in order to reload it.
PproxyToadlet.pluginNotFoundReloadTitle=Plugin Not Found (reloading)
@@ -714,9 +722,16 @@
PproxyToadlet.pluginUnloadedWithName=The plugin ${name} has been unloaded.
PproxyToadlet.plugins=Plugins
PproxyToadlet.pluginsWithNodeName=Plugins of ${name}
+PproxyToadlet.refreshOnStartup=Reload from server on startup
PproxyToadlet.reload=Reload
PproxyToadlet.returnToPluginPage=Return to plugin page
PproxyToadlet.startedAtTitle=Started at
+PproxyToadlet.startingPluginsTitle=Starting Plugins
+PproxyToadlet.startingPluginName=Plugin name
+PproxyToadlet.startingPluginStatus=Current status
+PproxyToadlet.startingPluginStatus.downloading=downloading
+PproxyToadlet.startingPluginStatus.starting=starting
+PproxyToadlet.startingPluginTime=Time spent
PproxyToadlet.pluginDirectoryNotCreated=The plugin directory could not be
created.
PproxyToadlet.pluginNotDownloaded=The plugin could not be downloaded.
PproxyToadlet.pluginStopping=Plugin Stopping
Modified: trunk/freenet/src/freenet/node/TextModeClientInterface.java
===================================================================
--- trunk/freenet/src/freenet/node/TextModeClientInterface.java 2007-10-28
18:33:01 UTC (rev 15623)
+++ trunk/freenet/src/freenet/node/TextModeClientInterface.java 2007-10-28
18:44:08 UTC (rev 15624)
@@ -878,22 +878,19 @@
probeAll();
} else if(uline.startsWith("PLUGLOAD:")) {
if (line.substring("PLUGLOAD:".length()).trim().equals("?")) {
- outsb.append(" PLUGLOAD: pkg.Class -
Load plugin from current classpath");
- outsb.append(" PLUGLOAD: pkg.Class at file:<filename>
- Load plugin from file");
- outsb.append(" PLUGLOAD: pkg.Class at http://...
- Load plugin from online file");
- outsb.append(" PLUGLOAD: *@... -
Load plugin from manifest in given jarfile");
+ outsb.append(" PLUGLOAD: pluginName - Load
official plugin from freenetproject.org");
+ outsb.append(" PLUGLOAD: file://<filename> - Load
plugin from file");
+ outsb.append(" PLUGLOAD: http://... - Load
plugin from online file");
outsb.append("");
- outsb.append("If the filename/url ends with \".url\",
it" +
- " is treated as a link, meaning that
the first line is" +
- " the accual URL. Else it is loaded as
classpath and" +
- " the class it loaded from it (meaning
the file could" +
- " be either a jar-file or a
class-file).");
- outsb.append("");
- outsb.append(" PLUGLOAD: pkg.Class* - Load newest
version of plugin from http://downloads.freenetproject.org/alpha/plugins/");
- outsb.append("");
-
- } else
-
n.pluginManager.startPlugin(line.substring("PLUGLOAD:".length()).trim(), true);
+ outsb.append("If you append as asterisk (\"*\") to the
name or URL, the plugin will be reloaded from the remote server on startup.");
+ } else {
+ String name =
line.substring("PLUGLOAD:".length()).trim();
+ boolean refresh = name.endsWith("*");
+ if (refresh) {
+ name = name.substring(0, name.length() - 1);
+ }
+ n.pluginManager.startPlugin(name, refresh, true);
+ }
//outsb.append("PLUGLOAD: <pkg.classname>[(@<URI to
jarfile.jar>|<<URI to file containing real URI>|* (will load from freenets
pluginpool))] - Load plugin.");
} else if(uline.startsWith("PLUGLIST")) {
outsb.append(n.pluginManager.dumpPlugins());
Modified: trunk/freenet/src/freenet/pluginmanager/PluginHandler.java
===================================================================
--- trunk/freenet/src/freenet/pluginmanager/PluginHandler.java 2007-10-28
18:33:01 UTC (rev 15623)
+++ trunk/freenet/src/freenet/pluginmanager/PluginHandler.java 2007-10-28
18:44:08 UTC (rev 15624)
@@ -19,8 +19,8 @@
*
* @param plug
*/
- public static PluginInfoWrapper startPlugin(PluginManager pm, String
filename, FredPlugin plug, PluginRespirator pr) {
- final PluginInfoWrapper pi = new PluginInfoWrapper(plug,
filename);
+ public static PluginInfoWrapper startPlugin(PluginManager pm, String
filename, FredPlugin plug, PluginRespirator pr, boolean refresh) {
+ final PluginInfoWrapper pi = new PluginInfoWrapper(plug,
filename, refresh);
final PluginStarter ps = new PluginStarter(pr, pi);
ps.setPlugin(pm, plug);
// We must start the plugin *after startup has finished*
Modified: trunk/freenet/src/freenet/pluginmanager/PluginInfoWrapper.java
===================================================================
--- trunk/freenet/src/freenet/pluginmanager/PluginInfoWrapper.java
2007-10-28 18:33:01 UTC (rev 15623)
+++ trunk/freenet/src/freenet/pluginmanager/PluginInfoWrapper.java
2007-10-28 18:44:08 UTC (rev 15624)
@@ -19,13 +19,14 @@
private boolean isThreadlessPlugin;
private boolean isIPDetectorPlugin;
private boolean isPortForwardPlugin;
+ private boolean autoRefresh;
private String filename;
private HashSet toadletLinks=new HashSet();
private boolean stopping = false;
private boolean unregistered = false;
//public String
- public PluginInfoWrapper(FredPlugin plug, String filename) {
+ public PluginInfoWrapper(FredPlugin plug, String filename, boolean
autoRefresh) {
this.plug = plug;
if (fedPluginThread) return;
className = plug.getClass().toString();
@@ -37,8 +38,20 @@
isThreadlessPlugin = (plug instanceof FredPluginThreadless);
isIPDetectorPlugin = (plug instanceof FredPluginIPDetector);
isPortForwardPlugin = (plug instanceof FredPluginPortForward);
+ this.autoRefresh = autoRefresh;
}
-
+
+ /**
+ * Returns whether this plugin should be refreshed from the server on
+ * startup.
+ *
+ * @return <code>true</code> if the plugin should be refresh on startup,
+ * <code>false</code> otherwise
+ */
+ public boolean isAutoRefresh() {
+ return autoRefresh;
+ }
+
void setThread(Thread ps) {
if(thread != null)
throw new IllegalStateException("Already set a thread");
Modified: trunk/freenet/src/freenet/pluginmanager/PluginManager.java
===================================================================
--- trunk/freenet/src/freenet/pluginmanager/PluginManager.java 2007-10-28
18:33:01 UTC (rev 15623)
+++ trunk/freenet/src/freenet/pluginmanager/PluginManager.java 2007-10-28
18:44:08 UTC (rev 15624)
@@ -4,10 +4,14 @@
package freenet.pluginmanager;
import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.io.OutputStream;
import java.net.JarURLConnection;
+import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
@@ -17,7 +21,12 @@
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarException;
import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.zip.ZipException;
import freenet.config.InvalidConfigValueException;
import freenet.config.SubConfig;
@@ -27,10 +36,13 @@
import freenet.node.Ticker;
import freenet.node.useralerts.SimpleUserAlert;
import freenet.node.useralerts.UserAlert;
+import freenet.support.JarClassLoader;
import freenet.support.Logger;
import freenet.support.URIPreEncoder;
import freenet.support.api.HTTPRequest;
import freenet.support.api.StringArrCallback;
+import freenet.support.io.Closer;
+import freenet.support.io.StreamCopier;
public class PluginManager {
@@ -45,7 +57,11 @@
*/
private final HashMap toadletList;
- private final Vector/*<PluginInfoWrapper>*/ pluginWrappers;
+
+ /* All currently starting plugins. */
+ private final Set/* <PluginProgress> */startingPlugins = new HashSet/*
<PluginProgress> */();
+
+ private final Vector/* <PluginInfoWrapper> */pluginWrappers;
private PluginRespirator pluginRespirator = null;
final Node node;
private final NodeClientCore core;
@@ -67,6 +83,7 @@
public String[] get() {
return getConfigLoadString();
}
+
public void set(String[] val) throws
InvalidConfigValueException {
//if(storeDir.equals(new File(val))) return;
// FIXME
@@ -76,9 +93,13 @@
String fns[] = pmconfig.getStringArr("loadplugin");
if (fns != null) {
- for (int i = 0 ; i < fns.length ; i++) {
- //System.err.println("Load: " +
StringArrOption.decode(fns[i]));
- startPlugin(fns[i], false);
+ for (int i = 0; i < fns.length; i++) {
+ String name = fns[i];
+ boolean refresh = name.endsWith("*");
+ if (refresh) {
+ name = name.substring(0, name.length()
- 1);
+ }
+ startPlugin(name, refresh, false);
}
}
@@ -98,8 +119,10 @@
Vector v = new Vector();
- while(it.hasNext())
-
v.add(((PluginInfoWrapper)it.next()).getFilename());
+ while (it.hasNext()) {
+ PluginInfoWrapper pluginInfoWrapper =
(PluginInfoWrapper) it.next();
+ v.add(pluginInfoWrapper.getFilename() +
(pluginInfoWrapper.isAutoRefresh() ? "*" : ""));
+ }
return (String[]) v.toArray(new String[v.size()]);
}catch (NullPointerException e){
@@ -108,35 +131,63 @@
}
}
- public void startPlugin(String filename, boolean store) {
+ /**
+ * Returns a set of all currently starting plugins.
+ *
+ * @return All currently starting plugins
+ */
+ public Set/* <PluginProgess> */getStartingPlugins() {
+ synchronized (startingPlugins) {
+ return new HashSet/* <PluginProgress>
*/(startingPlugins);
+ }
+ }
+
+ public void startPlugin(final String filename, final boolean refresh,
final boolean store) {
if (filename.trim().length() == 0)
return;
- Logger.normal(this, "Loading plugin: " + filename);
- FredPlugin plug;
- try {
- plug = LoadPlugin(filename);
- PluginInfoWrapper pi = PluginHandler.startPlugin(this,
filename, plug, pluginRespirator);
- synchronized (pluginWrappers) {
- pluginWrappers.add(pi);
+ final PluginProgress pluginProgress = new
PluginProgress(filename);
+ synchronized (startingPlugins) {
+ startingPlugins.add(pluginProgress);
+ }
+ node.executor.execute(new Runnable() {
+
+ public void run() {
+ Logger.normal(this, "Loading plugin: " +
filename);
+ FredPlugin plug;
+ try {
+ plug = loadPlugin(filename, refresh);
+
pluginProgress.setProgress(PluginProgress.STARTING);
+ PluginInfoWrapper pi =
PluginHandler.startPlugin(PluginManager.this, filename, plug, pluginRespirator,
refresh);
+ synchronized (pluginWrappers) {
+ pluginWrappers.add(pi);
+ }
+ Logger.normal(this, "Plugin loaded: " +
filename);
+ } catch (PluginNotFoundException e) {
+ Logger.normal(this, "Loading plugin
failed (" + filename + ')', e);
+ String message = e.getMessage();
+ core.alerts.register(new
SimpleUserAlert(true, l10n("pluginLoadingFailedTitle"),
l10n("pluginLoadingFailedWithMessage", new String[] { "name", "message" }, new
String[] { filename, message }), UserAlert.ERROR));
+ } catch (UnsupportedClassVersionError e) {
+ Logger.error(this, "Could not load
plugin " + filename + " : " + e, e);
+ System.err.println("Could not load
plugin " + filename + " : " + e);
+ e.printStackTrace();
+ String jvmVersion =
System.getProperty("java.vm.version");
+ if (jvmVersion.startsWith("1.4.") ||
jvmVersion.equals("1.4")) {
+ System.err.println("Plugin " +
filename + " appears to require a later JVM");
+ Logger.error(this, "Plugin " +
filename + " appears to require a later JVM");
+ core.alerts.register(new
SimpleUserAlert(true, l10n("pluginReqNewerJVMTitle", "name", filename),
l10n("pluginReqNewerJVM", "name", filename), UserAlert.ERROR));
+ }
+ } finally {
+ synchronized (startingPlugins) {
+
startingPlugins.remove(pluginProgress);
+ }
+ }
+ /* try not to destroy the config. */
+ synchronized (this) {
+ if (store)
+ core.storeConfig();
+ }
}
- Logger.normal(this, "Plugin loaded: " + filename);
- } catch (PluginNotFoundException e) {
- Logger.normal(this, "Loading plugin failed (" +
filename + ')', e);
- } catch (UnsupportedClassVersionError e) {
- Logger.error(this, "Could not load plugin "+filename+"
: "+e, e);
- System.err.println("Could not load plugin "+filename+"
: "+e);
- e.printStackTrace();
- String jvmVersion =
System.getProperty("java.vm.version");
- if(jvmVersion.startsWith("1.4.") ||
jvmVersion.equals("1.4")) {
- System.err.println("Plugin "+filename+" appears
to require a later JVM");
- Logger.error(this, "Plugin "+filename+" appears
to require a later JVM");
- core.alerts.register(new SimpleUserAlert(true,
- l10n("pluginReqNewerJVMTitle",
"name", filename),
- l10n("pluginReqNewerJVM",
"name", filename),
- UserAlert.ERROR));
- }
- }
- if(store) core.storeConfig();
+ }, "Plugin Starter");
}
void register(FredPlugin plug, PluginInfoWrapper pi) {
@@ -153,10 +204,38 @@
}
}
+ /**
+ * Returns the translation of the given key, prefixed by the short name
of
+ * the current class.
+ *
+ * @param key
+ * The key to fetch
+ * @return The translation
+ */
+ private String l10n(String key) {
+ return L10n.getString("PluginManager." + key);
+ }
+
private String l10n(String key, String pattern, String value) {
return L10n.getString("PluginManager."+key, pattern, value);
}
+ /**
+ * Returns the translation of the given key, replacing each occurence of
+ * <code>${<em>pattern</em>}</code> with <code>value</code>.
+ *
+ * @param key
+ * The key to fetch
+ * @param patterns
+ * The patterns to replace
+ * @param values
+ * The values to substitute
+ * @return The translation
+ */
+ private String l10n(String key, String[] patterns, String[] values) {
+ return L10n.getString("PluginManager." + key, patterns, values);
+ }
+
private void registerToadlet(FredPlugin pl){
//toadletList.put(e.getStackTrace()[1].getClass().toString(),
pl);
synchronized (toadletList) {
@@ -313,180 +392,253 @@
}
/**
- * Method to load a plugin from the given path and return is as an
object.
- * Will accept filename to be of one of the following forms:
- * "classname" to load a class from the current classpath
- * "classame at file:/path/to/jarfile.jar" to load class from an other
jarfile.
- *
- * @param filename The filename to load from
- * @return An instanciated object of the plugin
- * @throws PluginNotFoundException If anything goes wrong.
+ * Tries to load a plugin from the given name. If the name only
contains the
+ * name of a plugin and the plugin should not be refreshed on startup
it is
+ * loaded from the plugin directory, if found, otherwise it's refresh
from
+ * the project server. If the name contains a complete url and the short
+ * file already exists in the plugin directory and the plugin should
not be
+ * refreshed, it's loaded from the plugin directory, otherwise it's
+ * retrieved from the remote server.
+ *
+ * @param name
+ * The specification of the plugin
+ * @param refresh
+ * Whether the file should be refreshed on startup
+ * @return An instanciated object of the plugin
+ * @throws PluginNotFoundException
+ * If anything goes wrong.
*/
- private FredPlugin LoadPlugin(String origFilename)
- throws PluginNotFoundException {
- logMINOR = Logger.shouldLog(Logger.MINOR, this);
- Class cls = null;
- for (int tries = 0; (tries <= 5) && (cls == null); tries++) {
- String filename = origFilename;
- if (filename.endsWith("*")) {
- filename =
"*@http://downloads.freenetproject.org/alpha/plugins/"
- +
filename.substring(filename.lastIndexOf(".") + 1,
-
filename.length() - 1) + ".jar.url";
- if (logMINOR)
- Logger.minor(this, "Rewritten to " +
filename);
+ private FredPlugin loadPlugin(String name, boolean refresh) throws
PluginNotFoundException {
+ /* check if name contains a URL. */
+ URL pluginUrl = null;
+ try {
+ pluginUrl = new URL(name);
+ } catch (MalformedURLException mue1) {
+ }
+ if (pluginUrl == null) {
+ try {
+ pluginUrl = new
URL("http://downloads.freenetproject.org/alpha/plugins/" + name + ".jar.url");
+ } catch (MalformedURLException mue1) {
+ Logger.error(this, "could not build plugin url
for " + name, mue1);
+ throw new PluginNotFoundException("could not
build plugin url for " + name, mue1);
}
+ }
+
+ /* check for plugin directory. */
+ File pluginDirectory = new File("plugins");
+ if ((pluginDirectory.exists() &&
!pluginDirectory.isDirectory()) || (!pluginDirectory.exists() &&
!pluginDirectory.mkdirs())) {
+ Logger.error(this, "could not create plugin directory");
+ throw new PluginNotFoundException("could not create
plugin directory");
+ }
+
+ /* get plugin filename. */
+ String completeFilename = pluginUrl.getPath();
+ String filename =
completeFilename.substring(completeFilename.lastIndexOf('/') + 1);
+ File pluginFile = new File(pluginDirectory, filename);
+
+ /* check if file needs to be downloaded. */
+ if (logMINOR) {
+ Logger.minor(this, "plugin file " +
pluginFile.getAbsolutePath() + " exists: " + pluginFile.exists());
+ }
+ if (refresh || !pluginFile.exists()) {
+ File tempPluginFile = null;
+ OutputStream pluginOutputStream = null;
+ URLConnection urlConnection = null;
+ InputStream pluginInputStream = null;
try {
- BufferedReader in = null;
- InputStream is = null;
- if ((filename.indexOf("@") >= 0)) {
- boolean assumeURLRedirect = true;
- // Open from external file
- try {
- String realURL = null;
- String realClass = null;
+ tempPluginFile = File.createTempFile("plugin-",
".jar", pluginDirectory);
+ pluginOutputStream = new
FileOutputStream(tempPluginFile);
+ urlConnection = pluginUrl.openConnection();
+ urlConnection.setUseCaches(false);
+ urlConnection.setReadTimeout(0);
+ urlConnection.setAllowUserInteraction(false);
+ urlConnection.setConnectTimeout(180 * 1000);
+ urlConnection.connect();
+ pluginInputStream =
urlConnection.getInputStream();
+ byte[] buffer = new byte[1024];
+ int read;
+ while ((read = pluginInputStream.read(buffer))
!= -1) {
+ System.out.println("read " + read + "
bytes");
+ pluginOutputStream.write(buffer, 0,
read);
+ }
+ } catch (IOException ioe1) {
+ Logger.error(this, "could not load plugin",
ioe1);
+ if (tempPluginFile != null) {
+ tempPluginFile.delete();
+ }
+ throw new PluginNotFoundException("could not
load plugin: " + ioe1.getMessage(), ioe1);
+ } finally {
+ Closer.close(pluginOutputStream);
+ Closer.close(pluginInputStream);
+ }
+ /* move temp jar to final jar. */
+ if (pluginFile.exists()) {
+ if (!pluginFile.delete()) {
+ Logger.error(this, "could not remove
old plugin file");
+ throw new
PluginNotFoundException("could not remove old plugin file");
+ }
+ }
+ if (!tempPluginFile.renameTo(pluginFile)) {
+ Logger.error(this, "could not rename temp file
to plugin file");
+ throw new PluginNotFoundException("could not
rename temp file to plugin file");
+ }
+ }
- // Load the jar-file
- String[] parts =
filename.split("@");
- if (parts.length != 2) {
- throw new
PluginNotFoundException(
- "Could not split at
\"@\".");
- }
- realClass = parts[0];
- realURL = parts[1];
- if (logMINOR)
- Logger.minor(this,
"Class: " + realClass + " URL: "
- +
realURL);
+ /* now get the manifest file. */
+ JarFile pluginJarFile = null;
+ String pluginMainClassName = null;
+ try {
+ pluginJarFile = new JarFile(pluginFile);
+ Manifest manifest = pluginJarFile.getManifest();
+ if (manifest == null) {
+ Logger.error(this, "could not load manifest
from plugin file");
+ throw new PluginNotFoundException("could not
load manifest from plugin file");
+ }
+ Attributes pluginMainClassAttributes =
manifest.getMainAttributes();
+ if (pluginMainClassAttributes == null) {
+ Logger.error(this, "manifest does not contain
Plugin-Main-Class attribute");
+ throw new PluginNotFoundException("manifest
does not contain Plugin-Main-Class attribute");
+ }
+ pluginMainClassName =
pluginMainClassAttributes.getValue("Plugin-Main-Class");
+ } catch (JarException je1) {
+ Logger.error(this, "could not process jar file", je1);
+ throw new PluginNotFoundException("could not process
jar file", je1);
+ } catch (ZipException ze1) {
+ Logger.error(this, "could not process jar file", ze1);
+ throw new PluginNotFoundException("could not process
jar file", ze1);
+ } catch (IOException ioe1) {
+ Logger.error(this, "error processing jar file", ioe1);
+ throw new PluginNotFoundException("error procesesing
jar file", ioe1);
+ } finally {
+ Closer.close(pluginJarFile);
+ }
- if (filename.endsWith(".url")) {
- if (!assumeURLRedirect)
{
- // Load the
txt-file
- URL url = new
URL(parts[1]);
- URLConnection
uc = url.openConnection();
- in = new
BufferedReader(new InputStreamReader(
-
uc.getInputStream()));
+ try {
+ JarClassLoader jarClassLoader = new
JarClassLoader(pluginFile);
+ Class pluginMainClass =
jarClassLoader.loadClass(pluginMainClassName);
+ Object object = pluginMainClass.newInstance();
+ if (!(object instanceof FredPlugin)) {
+ Logger.error(this, "plugin main class is not a
plugin");
+ throw new PluginNotFoundException("plugin main
class is not a plugin");
+ }
+ return (FredPlugin) object;
+ } catch (IOException ioe1) {
+ Logger.error(this, "could not load plugin", ioe1);
+ throw new PluginNotFoundException("could not load
plugin", ioe1);
+ } catch (ClassNotFoundException cnfe1) {
+ Logger.error(this, "could not find plugin class",
cnfe1);
+ throw new PluginNotFoundException("could not find
plugin class", cnfe1);
+ } catch (InstantiationException ie1) {
+ Logger.error(this, "could not instantiate plugin", ie1);
+ throw new PluginNotFoundException("could not
instantiate plugin", ie1);
+ } catch (IllegalAccessException iae1) {
+ Logger.error(this, "could not access plugin main
class", iae1);
+ throw new PluginNotFoundException("could not access
plugin main class", iae1);
+ }
+ }
- realURL =
in.readLine();
- if (realURL ==
null)
- throw
new PluginNotFoundException(
-
"Initialization error: "
-
+ url
-
+ " isn't a plugin loading url!");
- realURL =
realURL.trim();
- if (logMINOR)
-
Logger.minor(this, "Loaded new URL: "
-
+ realURL + " from .url file");
- in.close();
- }
- assumeURLRedirect =
!assumeURLRedirect;
- }
+ Ticker getTicker() {
+ return node.getTicker();
+ }
- // Load the class inside file
- URL[] serverURLs = new URL[] {
new URL(realURL) };
- ClassLoader cl = new
URLClassLoader(serverURLs);
+ /**
+ * Tracks the progress of loading and starting a plugin.
+ *
+ * @author David ‘Bombe’ Roden <bombe at
freenetproject.org>
+ * @version $Id$
+ */
+ public static class PluginProgress {
- // Handle automatic fetching of
pluginclassname
- if (realClass.equals("*")) {
+ /** State for downloading. */
+ public static final PluginProgress DOWNLOADING = new
PluginProgress();
- // Clean URL
- URI liburi =
URIPreEncoder.encodeURI(realURL);
- if (logMINOR)
-
Logger.minor(this, "cleaned url: " + realURL
-
+ " -> " + liburi.toString());
- realURL =
liburi.toString();
+ /** State for starting. */
+ public static final PluginProgress STARTING = new
PluginProgress();
- URL url = new
URL("jar:" + realURL + "!/");
- JarURLConnection
jarConnection = (JarURLConnection) url
- .openConnection();
- // Java seems to cache
even file: urls...
-
jarConnection.setUseCaches(false);
- JarFile jf =
jarConnection.getJarFile();
- // URLJarFile jf = new
URLJarFile(new File(liburi));
- // is =
- //
jf.getInputStream(jf.getJarEntry("META-INF/MANIFEST.MF"));
+ /** The starting time. */
+ private long startingTime = System.currentTimeMillis();
- // BufferedReader
manifest = new BufferedReader(new
- //
InputStreamReader(cl.getResourceAsStream("/META-INF/MANIFEST.MF")));
+ /** The current state. */
+ private PluginProgress pluginProgress;
- // URL url = new
URL(parts[1]);
- // URLConnection uc =
- //
cl.getResource("/META-INF/MANIFEST.MF").openConnection();
+ /** The name by which the plugin is loaded. */
+ private String name;
- is =
jf.getInputStream(jf
-
.getJarEntry("META-INF/MANIFEST.MF"));
- in = new
BufferedReader(new InputStreamReader(is));
- String line;
- while ((line =
in.readLine()) != null) {
- //
System.err.println(line + "\t\t\t" +
- // realClass);
- if
(line.startsWith("Plugin-Main-Class: ")) {
-
realClass = line.substring(
-
"Plugin-Main-Class: ".length())
-
.trim();
- if
(logMINOR)
-
Logger.minor(this,
-
"Found plugin main class "
-
+ realClass
-
+ " from manifest");
- }
- }
- //
System.err.println("Real classname: " +
- // realClass);
- }
+ /**
+ * Private constructor for state constants.
+ */
+ private PluginProgress() {
+ }
- cls = cl.loadClass(realClass);
+ /**
+ * Creates a new progress tracker for a plugin that is loaded
by the
+ * given name.
+ *
+ * @param name
+ * The name by which the plugin is loaded
+ */
+ PluginProgress(String name) {
+ this.name = name;
+ pluginProgress = DOWNLOADING;
+ }
- } finally {
- try {
- if (is != null)
- is.close();
- if (in != null)
- in.close();
- } catch (IOException ioe) {
- }
- }
- } else {
- // Load class
- try {
- cls = Class.forName(filename);
- } catch (ClassNotFoundException e) {
- throw new
PluginNotFoundException(filename);
- }
- }
+ /**
+ * Returns the number of milliseconds this plugin is already
being
+ * loaded.
+ *
+ * @return The time this plugin is already being loaded (in
+ * milliseconds)
+ */
+ public long getTime() {
+ return System.currentTimeMillis() - startingTime;
+ }
- if (cls == null)
- throw new
PluginNotFoundException("Unknown error");
- } catch (Exception e) {
- Logger.normal(this, "Failed to load plugin " +
filename + " : "
- + e, e);
- if (tries >= 5)
- throw new
PluginNotFoundException("Initialization error:"
- + filename, e);
+ /**
+ * Returns the name by which the plugin is loaded.
+ *
+ * @return The name by which the plugin is loaded
+ */
+ public String getName() {
+ return name;
+ }
- try {
- Thread.sleep(100);
- } catch (Exception ee) {
- }
- }
+ /**
+ * Returns the current state of the plugin start procedure.
+ *
+ * @return The current state of the plugin
+ */
+ public PluginProgress getProgress() {
+ return pluginProgress;
}
- // Class loaded... Objectize it!
- Object o = null;
- try {
- o = cls.newInstance();
- } catch (Exception e) {
- throw new PluginNotFoundException("Could not re-create
plugin:"
- + origFilename, e);
+ /**
+ * Sets the current state of the plugin start procedure
+ *
+ * @param pluginProgress
+ * The current state
+ */
+ void setProgress(PluginProgress pluginProgress) {
+ this.pluginProgress = pluginProgress;
}
- // See if we have the right type
- if (!(o instanceof FredPlugin)) {
- throw new PluginNotFoundException("Not a plugin: " +
origFilename);
+ /**
+ * If this object is one of the constants {@link #DOWNLOADING}
or
+ * {@link #STARTING}, the name of those constants will be
returned,
+ * otherwise a textual representation of the plugin progress is
+ * returned.
+ *
+ * @return The name of a constant, or the plugin progress
+ */
+ /* @Override */
+ public String toString() {
+ if (this == DOWNLOADING) {
+ return "downloading";
+ } else if (this == STARTING) {
+ return "starting";
+ }
+ return "PluginProgress[name=" + name + ",startingTime="
+ startingTime + ",progress=" + pluginProgress + "]";
}
- return (FredPlugin) o;
}
- Ticker getTicker() {
- return node.getTicker();
- }
}
Added: trunk/freenet/src/freenet/support/JarClassLoader.java
===================================================================
--- trunk/freenet/src/freenet/support/JarClassLoader.java
(rev 0)
+++ trunk/freenet/src/freenet/support/JarClassLoader.java 2007-10-28
18:44:08 UTC (rev 15624)
@@ -0,0 +1,162 @@
+/*
+ * freenet - JarClassLoader.java Copyright ? 2007 David Roden
+ *
+ * This program is free software; you can redistribute it and/or modify it
under
+ * the terms of the GNU General Public License as published by the Free
Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package freenet.support;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import freenet.support.io.StreamCopier;
+
+/**
+ * Class loader that loads classes from a JAR file. The JAR file gets copied
+ * to a temporary location; requests for classes and resources from this class
+ * loader are then satisfied from this local copy.
+ *
+ * @author <a href="mailto:dr at ina-germany.de">David Roden</a>
+ * @version $Id$
+ */
+public class JarClassLoader extends ClassLoader {
+
+ /** The temporary jar file. */
+ private JarFile tempJarFile;
+
+ /**
+ * Constructs a new jar class loader that loads classes from the jar
file
+ * with the given name in the local file system.
+ *
+ * @param fileName
+ * The name of the jar file
+ * @throws IOException
+ * if an I/O error occurs
+ */
+ public JarClassLoader(String fileName) throws IOException {
+ this(new File(fileName));
+ }
+
+ /**
+ * Constructs a new jar class loader that loads classes from the
specified
+ * URL.
+ *
+ * @param fileUrl
+ * The URL to load the jar file from
+ * @param length
+ * The length of the jar file if known, <code>-1</code>
+ * otherwise
+ * @throws IOException
+ * if an I/O error occurs
+ */
+ public JarClassLoader(URL fileUrl, long length) throws IOException {
+ copyFileToTemp(fileUrl.openStream(), length);
+ }
+
+ /**
+ * Constructs a new jar class loader that loads classes from the
specified
+ * file.
+ *
+ * @param file
+ * The file to load classes from
+ * @throws IOException
+ * if an I/O error occurs
+ */
+ public JarClassLoader(File file) throws IOException {
+ tempJarFile = new JarFile(file);
+ }
+
+ /**
+ * Copies the contents of the input stream (which are supposed to be the
+ * contents of a jar file) to a temporary location.
+ *
+ * @param inputStream
+ * The input stream to read from
+ * @param length
+ * The length of the stream if known, <code>-1</code> if the
+ * length is not known
+ * @throws IOException
+ * if an I/O error occurs
+ */
+ private void copyFileToTemp(InputStream inputStream, long length)
throws IOException {
+ File tempFile = File.createTempFile("jar-", ".tmp");
+ FileOutputStream fileOutputStream = new
FileOutputStream(tempFile);
+ StreamCopier.copy(inputStream, fileOutputStream, length);
+ fileOutputStream.close();
+ tempFile.deleteOnExit();
+ tempJarFile = new JarFile(tempFile);
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This method searches the temporary copy of the jar file for an entry
+ * that is specified by the given class name.
+ *
+ * @see java.lang.ClassLoader#findClass(java.lang.String)
+ */
+ protected Class findClass(String name) throws ClassNotFoundException {
+ try {
+ String pathName = transformName(name);
+ JarEntry jarEntry = tempJarFile.getJarEntry(pathName);
+ if (jarEntry != null) {
+ long size = jarEntry.getSize();
+ InputStream jarEntryInputStream =
tempJarFile.getInputStream(jarEntry);
+ ByteArrayOutputStream classBytesOutputStream =
new ByteArrayOutputStream((int) size);
+ StreamCopier.copy(jarEntryInputStream,
classBytesOutputStream, size);
+ classBytesOutputStream.close();
+ byte[] classBytes =
classBytesOutputStream.toByteArray();
+ Class clazz = defineClass(name, classBytes, 0,
classBytes.length);
+ return clazz;
+ }
+ return null;
+ } catch (IOException e) {
+ throw new ClassNotFoundException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.ClassLoader#findResource(java.lang.String)
+ */
+ protected URL findResource(String name) {
+ try {
+ return new URL("jar:" + new
File(tempJarFile.getName()).toURL() + "!" + name);
+ } catch (MalformedURLException e) {
+ }
+ return null;
+ }
+
+ /**
+ * Transforms the class name into a file name that can be used to locate
+ * an entry in the jar file.
+ *
+ * @param name
+ * The name of the class
+ * @return The path name of the entry in the jar file
+ */
+ private String transformName(String name) {
+ return name.replace('.', '/') + ".class";
+ }
+
+}
Property changes on: trunk/freenet/src/freenet/support/JarClassLoader.java
___________________________________________________________________
Name: svn:keywords
+ Id
Added: trunk/freenet/src/freenet/support/io/Closer.java
===================================================================
--- trunk/freenet/src/freenet/support/io/Closer.java
(rev 0)
+++ trunk/freenet/src/freenet/support/io/Closer.java 2007-10-28 18:44:08 UTC
(rev 15624)
@@ -0,0 +1,82 @@
+/*
+ * freenet - Closer.java Copyright ? 2007 David Roden
+ *
+ * This program is free software; you can redistribute it and/or modify it
under
+ * the terms of the GNU General Public License as published by the Free
Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package freenet.support.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.jar.JarFile;
+
+/**
+ * Closes various resources. The resources are checked for being
+ * <code>null</code> before being closed, and every possible execption is
+ * swallowed. That makes this class perfect for use in the finally blocks of
+ * try-catch-finally blocks.
+ *
+ * @author David ‘Roden’ <bombe at freenetproject.org>
+ * @version $Id$
+ */
+public class Closer {
+
+ /**
+ * Closes the given output stream.
+ *
+ * @param outputStream
+ * The output stream to close
+ */
+ public static void close(OutputStream outputStream) {
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException ioe1) {
+ }
+ }
+ }
+
+ /**
+ * Closes the given input stream.
+ *
+ * @param inputStream
+ * The input stream to close
+ */
+ public static void close(InputStream inputStream) {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException ioe1) {
+ }
+ }
+ }
+
+ /**
+ * Closes the given jar file.
+ *
+ * @param jarFile
+ * The jar file to close
+ */
+ public static void close(JarFile jarFile) {
+ if (jarFile != null) {
+ try {
+ jarFile.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+}
Property changes on: trunk/freenet/src/freenet/support/io/Closer.java
___________________________________________________________________
Name: svn:keywords
+ Id
Added: trunk/freenet/src/freenet/support/io/StreamCopier.java
===================================================================
--- trunk/freenet/src/freenet/support/io/StreamCopier.java
(rev 0)
+++ trunk/freenet/src/freenet/support/io/StreamCopier.java 2007-10-28
18:44:08 UTC (rev 15624)
@@ -0,0 +1,123 @@
+/*
+ * freenet - StreamCopier.java Copyright ? 2007 David Roden
+ *
+ * This program is free software; you can redistribute it and/or modify it
under
+ * the terms of the GNU General Public License as published by the Free
Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package freenet.support.io;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Helper class that copies bytes from an {@link InputStream} to an
+ * {@link OutputStream}.
+ *
+ * @author David ‘Roden’ <bombe at freenetproject.org>
+ * @version $Id$
+ */
+public class StreamCopier {
+
+ /** Default buffer size is 64k. */
+ private static final int DEFAULT_BUFFER_SIZE = 1 << 16;
+
+ /** The current buffer size. */
+ private static int bufferSize = DEFAULT_BUFFER_SIZE;
+
+ /**
+ * Sets the buffer size for following transfers.
+ *
+ * @param bufferSize
+ * The new buffer size
+ */
+ public static void setBufferSize(int bufferSize) {
+ StreamCopier.bufferSize = bufferSize;
+ }
+
+ /**
+ * Copies <code>length</code> bytes from the source input stream to the
+ * destination output stream. If <code>length</code> is <code>-1</code>
+ * as much bytes as possible will be copied (i.e. until
+ * {@link InputStream#read()} returns <code>-1</code> to signal the end
of
+ * the stream).
+ *
+ * @param source
+ * The input stream to read from
+ * @param destination
+ * The output stream to write to
+ * @param length
+ * The number of bytes to copy
+ * @throws IOException
+ * if an I/O error occurs
+ */
+ public static void copy(InputStream source, OutputStream destination,
long length) throws IOException {
+ long remaining = length;
+ byte[] buffer = new byte[bufferSize];
+ int read = 0;
+ while ((remaining == -1) || (remaining > 0)) {
+ read = source.read(buffer, 0, ((remaining > bufferSize)
|| (remaining == -1)) ? bufferSize : (int) remaining);
+ if (read == -1) {
+ if (length == -1) {
+ return;
+ }
+ throw new EOFException("stream reached eof");
+ }
+ destination.write(buffer, 0, read);
+ remaining -= read;
+ }
+ }
+
+ /**
+ * Copies as much bytes as possible (i.e. until {@link
InputStream#read()}
+ * returns <code>-1</code>) from the source input stream to the
+ * destination output stream.
+ *
+ * @param source
+ * The input stream to read from
+ * @param destination
+ * The output stream to write to
+ * @throws IOException
+ * if an I/O error occurs
+ */
+ public static void copy(InputStream source, OutputStream destination)
throws IOException {
+ copy(source, destination, -1);
+ }
+
+ /**
+ * Find the length of an input stream. This method will consume the
complete
+ * input stream until its {@link InputStream#read(byte[])} method
returns
+ * <code>-1</code>, thus signalling the end of the stream.
+ *
+ * @param source
+ * The input stream to find the length of
+ * @return The numbe of bytes that can be read from the stream
+ * @throws IOException
+ * if an I/O error occurs
+ */
+ public static long findLength(InputStream source) throws IOException {
+ long length = 0;
+ byte[] buffer = new byte[bufferSize];
+ int read = 0;
+ while (read > -1) {
+ read = source.read(buffer);
+ if (read != -1) {
+ length += read;
+ }
+ }
+ return length;
+ }
+
+}
Property changes on: trunk/freenet/src/freenet/support/io/StreamCopier.java
___________________________________________________________________
Name: svn:keywords
+ Id