PluginProgress:
- toString() is fed into l10n somewhere. It's not always something which *can*
be l10n'ed (it may be PluginProgress[name=...). You should deal with this.
- The constants should really be of a different type to the containing class.
Other than that, good stuff! I wonder what the purpose of the new
JarClassLoader class is?
On Sunday 28 October 2007 18:44, you wrote:
> 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
>
> _______________________________________________
> cvs mailing list
> cvs at freenetproject.org
> http://emu.freenetproject.org/cgi-bin/mailman/listinfo/cvs
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: not available
URL:
<https://emu.freenetproject.org/pipermail/devl/attachments/20071113/a1f3e6ca/attachment.pgp>