Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPascal Rapicault2011-01-18 20:26:59 +0000
committerPascal Rapicault2011-01-18 20:26:59 +0000
commit7f522e530cf0d6f3b28c98c202cb5c58fa9c27bb (patch)
tree434157af47bbd10d631c5c36ad24e6b643d4a595 /bundles/org.eclipse.equinox.p2.discovery.compatibility
parentf15fa1c97659feb2651680e82466dbad24bc22fa (diff)
downloadrt.equinox.p2-7f522e530cf0d6f3b28c98c202cb5c58fa9c27bb.tar.gz
rt.equinox.p2-7f522e530cf0d6f3b28c98c202cb5c58fa9c27bb.tar.xz
rt.equinox.p2-7f522e530cf0d6f3b28c98c202cb5c58fa9c27bb.zip
[discovery] Cache jars downloaded by RemoteBundleDiscoveryStrategyv20110118
Diffstat (limited to 'bundles/org.eclipse.equinox.p2.discovery.compatibility')
-rw-r--r--bundles/org.eclipse.equinox.p2.discovery.compatibility/META-INF/MANIFEST.MF2
-rw-r--r--bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/Activator.java47
-rw-r--r--bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/Messages.java13
-rw-r--r--bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/RemoteBundleDiscoveryStrategy.java7
-rw-r--r--bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/messages.properties6
-rw-r--r--bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/util/CacheManager.java219
-rw-r--r--bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/util/TransportUtil.java46
7 files changed, 327 insertions, 13 deletions
diff --git a/bundles/org.eclipse.equinox.p2.discovery.compatibility/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.discovery.compatibility/META-INF/MANIFEST.MF
index ae53b85ea..43134b1e2 100644
--- a/bundles/org.eclipse.equinox.p2.discovery.compatibility/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.discovery.compatibility/META-INF/MANIFEST.MF
@@ -13,3 +13,5 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="3.3.0",
Export-Package: org.eclipse.equinox.internal.p2.discovery.compatibility;x-friends:="org.eclipse.equinox.p2.ui.discovery",
org.eclipse.equinox.internal.p2.discovery.compatibility.util;x-friends:="org.eclipse.equinox.p2.ui.discovery"
Bundle-Localization: plugin
+Bundle-Activator: org.eclipse.equinox.internal.p2.discovery.compatibility.Activator
+Bundle-ActivationPolicy: lazy
diff --git a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/Activator.java b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/Activator.java
new file mode 100644
index 000000000..6aa66026f
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/Activator.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Sonatype, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Sonatype, Inc. - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.equinox.internal.p2.discovery.compatibility;
+
+import org.eclipse.core.runtime.Plugin;
+import org.eclipse.equinox.internal.p2.discovery.compatibility.util.CacheManager;
+import org.eclipse.equinox.internal.p2.transport.ecf.RepositoryTransport;
+import org.osgi.framework.BundleContext;
+
+public class Activator extends Plugin {
+
+ private static Activator plugin;
+
+ private CacheManager manager;
+
+ public static final String ID = "org.eclipse.equinox.p2.discovery.compatibility"; //$NON-NLS-1$
+
+ public void start(BundleContext context) throws Exception {
+ super.start(context);
+ plugin = this;
+ }
+
+ public void stop(BundleContext context) throws Exception {
+ super.stop(context);
+ plugin = null;
+ }
+
+ public static Activator getDefault() {
+ return plugin;
+ }
+
+ public synchronized CacheManager getCacheManager() {
+ if (manager == null) {
+ manager = new CacheManager(new RepositoryTransport());
+ }
+ return manager;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/Messages.java b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/Messages.java
index 3e334b23c..5626eaa6e 100644
--- a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/Messages.java
+++ b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/Messages.java
@@ -7,6 +7,7 @@
*
* Contributors:
* Tasktop Technologies - initial API and implementation
+ * Sonatype, Inc. - added caching support
*******************************************************************************/
package org.eclipse.equinox.internal.p2.discovery.compatibility;
@@ -16,7 +17,7 @@ import org.eclipse.osgi.util.NLS;
/**
* @author David Green
*/
-class Messages extends NLS {
+public class Messages extends NLS {
private static final String BUNDLE_NAME = "org.eclipse.equinox.internal.p2.discovery.compatibility.messages"; //$NON-NLS-1$
@@ -30,6 +31,14 @@ class Messages extends NLS {
public static String BundleDiscoveryStrategy_unexpected_element;
+ public static String CacheManager_AuthenticationFaileFor_0;
+
+ public static String CacheManager_FailedCommunication_0;
+
+ public static String CacheManager_Neither_0_nor_1_found;
+
+ public static String CacheManage_ErrorRenamingCache;
+
public static String ConnectorDiscoveryExtensionReader_Documents;
public static String ConnectorDiscoveryExtensionReader_Tasks;
@@ -74,6 +83,8 @@ class Messages extends NLS {
public static String SiteVerifier_Verify_Job_Label;
+ public static String TransportUtil_InternalError;
+
static {
// initialize resource bundle
NLS.initializeMessages(BUNDLE_NAME, Messages.class);
diff --git a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/RemoteBundleDiscoveryStrategy.java b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/RemoteBundleDiscoveryStrategy.java
index ddafba1f3..707aa9471 100644
--- a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/RemoteBundleDiscoveryStrategy.java
+++ b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/RemoteBundleDiscoveryStrategy.java
@@ -7,6 +7,7 @@
*
* Contributors:
* Tasktop Technologies - initial API and implementation
+ * Sonatype, Inc. - added caching support
*******************************************************************************/
package org.eclipse.equinox.internal.p2.discovery.compatibility;
@@ -192,10 +193,6 @@ public class RemoteBundleDiscoveryStrategy extends BundleDiscoveryStrategy {
String bundleUrl = entry.getLocation();
for (int attemptCount = 0; attemptCount < maxDiscoveryJarDownloadAttempts; ++attemptCount) {
try {
- if (!bundleUrl.startsWith("http://") && !bundleUrl.startsWith("https://")) { //$NON-NLS-1$//$NON-NLS-2$
- LogHelper.log(new Status(IStatus.WARNING, DiscoveryCore.ID_PLUGIN, NLS.bind(Messages.RemoteBundleDiscoveryStrategy_unrecognized_discovery_url, bundleUrl)));
- continue;
- }
String lastPathElement = bundleUrl.lastIndexOf('/') == -1 ? bundleUrl : bundleUrl.substring(bundleUrl.lastIndexOf('/'));
File target = File.createTempFile(lastPathElement.replaceAll("^[a-zA-Z0-9_.]", "_") + "_", ".jar", temporaryStorage); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$
@@ -217,6 +214,8 @@ public class RemoteBundleDiscoveryStrategy extends BundleDiscoveryStrategy {
if (isUnknownHostException(e)) {
break;
}
+ } catch (CoreException e) {
+ LogHelper.log(new Status(IStatus.ERROR, DiscoveryCore.ID_PLUGIN, NLS.bind(Messages.RemoteBundleDiscoveryStrategy_cannot_download_bundle, bundleUrl, e.getMessage()), e));
}
}
return this;
diff --git a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/messages.properties b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/messages.properties
index d4c1a86b4..f20bfad87 100644
--- a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/messages.properties
+++ b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/messages.properties
@@ -7,12 +7,17 @@
#
# Contributors:
# Tasktop Technologies - initial API and implementation
+# Sonatype, Inc. - added caching support
###############################################################################
BundleDiscoveryStrategy_3={0}: {1}
BundleDiscoveryStrategy_categoryDisallowed=Cannot create category ''{0}'' with id ''{1}'' from {2}: disallowed
BundleDiscoveryStrategy_task_loading_local_extensions=Loading local extensions
BundleDiscoveryStrategy_task_processing_extensions=Processing extensions
BundleDiscoveryStrategy_unexpected_element=unexpected element ''{0}''
+CacheManager_Neither_0_nor_1_found=Neither {0} nor {1} found.
+CacheManager_AuthenticationFaileFor_0=Authentication failed for {0}.
+CacheManager_FailedCommunication_0=Communication with {0} failed.
+CacheManage_ErrorRenamingCache=An error occurred while downloading {0}. The cache file {1} could not be renamed to {2}.
ConnectorDiscoveryExtensionReader_Documents=Documents
ConnectorDiscoveryExtensionReader_Tasks=Tasks
ConnectorDiscoveryExtensionReader_unexpected_element_icon=Unexpected element icon
@@ -35,3 +40,4 @@ RemoteBundleDiscoveryStrategy_unrecognized_discovery_url=Unrecognized discovery
SiteVerifier_Error_with_cause={0}: {1}
SiteVerifier_Unexpected_Error=Unexpected error while verifying site availability
SiteVerifier_Verify_Job_Label=Verifying availability
+TransportUtil_InternalError=Internal Error
diff --git a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/util/CacheManager.java b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/util/CacheManager.java
new file mode 100644
index 000000000..fba3ba1ef
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/util/CacheManager.java
@@ -0,0 +1,219 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Cloudsmith Inc - additional implementation
+ * Sonatype, Inc. - additional implementation and p2 discovery support
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.discovery.compatibility.util;
+
+import java.io.*;
+import java.net.URI;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
+import org.eclipse.equinox.internal.p2.discovery.compatibility.Activator;
+import org.eclipse.equinox.internal.p2.discovery.compatibility.Messages;
+import org.eclipse.equinox.internal.p2.repository.AuthenticationFailedException;
+import org.eclipse.equinox.internal.p2.repository.Transport;
+import org.eclipse.equinox.internal.provisional.p2.repository.IStateful;
+import org.eclipse.equinox.p2.core.ProvisionException;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * A class to manage discovery cache files. Creating the cache files will place
+ * the file in the plugin state location in a cache directory.
+ */
+@SuppressWarnings("restriction")
+public class CacheManager {
+
+ private static final String PREFIX = "discovery"; //$NON-NLS-1$
+
+ private static final String DOWNLOADING = "downloading"; //$NON-NLS-1$
+
+ private final Transport transport;
+
+ /**
+ * IStateful implementation of BufferedOutputStream. Class is used to get the status from
+ * a download operation.
+ */
+ private static class StatefulStream extends BufferedOutputStream implements IStateful {
+ private IStatus status;
+
+ public StatefulStream(OutputStream stream) {
+ super(stream);
+ }
+
+ public IStatus getStatus() {
+
+ return status;
+ }
+
+ public void setStatus(IStatus aStatus) {
+ status = aStatus;
+ }
+
+ }
+
+ public CacheManager(Transport transport) {
+ this.transport = transport;
+ }
+
+ /**
+ * Returns a hash of the location.
+ */
+ private int computeHash(URI location) {
+ return location.hashCode();
+ }
+
+ /**
+ * Returns a local cache file with the contents of the given remote location,
+ * or <code>null</code> if a local cache could not be created.
+ *
+ * @param location the remote location to be cached
+ * @param monitor a progress monitor
+ * @return A {@link File} object pointing to the cache file or <code>null</code>
+ * @throws FileNotFoundException if neither jar nor xml index file exists at given location
+ * @throws AuthenticationFailedException if jar not available and xml causes authentication fail
+ * @throws IOException on general IO errors
+ * @throws ProvisionException on any error (e.g. user cancellation, unknown host, malformed address, connection refused, etc.)
+ * @throws OperationCanceledException - if user cancelled
+ */
+ public File createCache(URI location, IProgressMonitor monitor) throws IOException, ProvisionException {
+
+ SubMonitor submonitor = SubMonitor.convert(monitor, 1000);
+ try {
+ File cacheFile = getCache(location);
+
+ boolean stale = true;
+ long lastModified = 0L;
+
+ if (cacheFile != null) {
+ lastModified = cacheFile.lastModified();
+ }
+ // get last modified on jar
+ long lastModifiedRemote = 0L;
+ // bug 269588 - server may return 0 when file exists, so extra flag is needed
+ try {
+ lastModifiedRemote = transport.getLastModified(location, submonitor.newChild(1));
+ if (lastModifiedRemote <= 0)
+ LogHelper.log(new Status(IStatus.WARNING, Activator.ID, "Server returned lastModified <= 0 for " + location)); //$NON-NLS-1$
+ } catch (AuthenticationFailedException e) {
+ // it is not meaningful to continue - the credentials are for the server
+ // do not pass the exception - it gives no additional meaningful user information
+ throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_AUTHENTICATION, NLS.bind(Messages.CacheManager_AuthenticationFaileFor_0, location), null));
+ } catch (CoreException e) {
+ throw new ProvisionException(e.getStatus());
+ } catch (OperationCanceledException e) {
+ // must pass this on
+ throw e;
+ }
+ if (submonitor.isCanceled())
+ throw new OperationCanceledException();
+ stale = lastModifiedRemote > lastModified || lastModifiedRemote <= 0;
+
+ if (!stale)
+ return cacheFile;
+
+ // The cache is stale or missing, so we need to update it from the remote location
+ cacheFile = getCacheFile(location);
+ updateCache(cacheFile, location, lastModifiedRemote, submonitor);
+ return cacheFile;
+ } finally {
+ submonitor.done();
+ }
+ }
+
+ /**
+ * Deletes the local cache file(s) for the given location
+ * @param location
+ */
+ void deleteCache(URI location) {
+ File cacheFile = getCache(location);
+ // delete the cache file if it exists
+ safeDelete(cacheFile);
+ // delete a resumable download if it exists
+ safeDelete(new File(new File(cacheFile.getParentFile(), DOWNLOADING), cacheFile.getName()));
+ }
+
+ /**
+ * Determines the local file paths of the locations potential cache file.
+ * @param location The location to compute the cache for
+ * @param PREFIX The prefix to use for this location
+ * @return A {@link File} array with the cache files for JAR and XML extensions.
+ */
+ private File getCache(URI location) {
+ File cacheFile = getCacheFile(location);
+ return cacheFile.exists() ? cacheFile : null;
+ }
+
+ private File getCacheFile(URI location) {
+ return new File(getCacheDirectory(), PREFIX + computeHash(location));
+ }
+
+ /**
+ * Returns the file corresponding to the data area to be used by the cache manager.
+ */
+ protected File getCacheDirectory() {
+ return Activator.getDefault().getStateLocation().append("cache").toFile(); //$NON-NLS-1$
+ }
+
+ private boolean safeDelete(File file) {
+ if (file.exists()) {
+ if (!file.delete()) {
+ file.deleteOnExit();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected void updateCache(File cacheFile, URI remoteFile, long lastModifiedRemote, SubMonitor submonitor) throws FileNotFoundException, IOException, ProvisionException {
+ cacheFile.getParentFile().mkdirs();
+ File downloadDir = new File(cacheFile.getParentFile(), DOWNLOADING);
+ if (!downloadDir.exists())
+ downloadDir.mkdir();
+ File tempFile = new File(downloadDir, cacheFile.getName());
+ // Ensure that the file from a previous download attempt is removed
+ if (tempFile.exists())
+ safeDelete(tempFile);
+
+ tempFile.createNewFile();
+
+ StatefulStream stream = null;
+ try {
+ stream = new StatefulStream(new FileOutputStream(tempFile));
+ } catch (Exception e) {
+ throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, e.getMessage(), e));
+ }
+ IStatus result = null;
+ try {
+ submonitor.setWorkRemaining(1000);
+ result = transport.download(remoteFile, stream, submonitor.newChild(1000));
+ } catch (OperationCanceledException e) {
+ // need to pick up the status - a new operation canceled exception is thrown at the end
+ // as status will be CANCEL.
+ result = stream.getStatus();
+ } finally {
+ stream.close();
+ // If there was any problem fetching the file, delete the temp file
+ if (result == null || !result.isOK())
+ safeDelete(tempFile);
+ }
+ if (result.isOK()) {
+ if (cacheFile.exists())
+ safeDelete(cacheFile);
+ if (tempFile.renameTo(cacheFile))
+ return;
+ result = new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.CacheManage_ErrorRenamingCache, new Object[] {remoteFile.toString(), tempFile.getAbsolutePath(), cacheFile.getAbsolutePath()}));
+ }
+
+ if (result.getSeverity() == IStatus.CANCEL || submonitor.isCanceled())
+ throw new OperationCanceledException();
+ throw new ProvisionException(result);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/util/TransportUtil.java b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/util/TransportUtil.java
index a0ccbd008..be0fdf1d0 100644
--- a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/util/TransportUtil.java
+++ b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/util/TransportUtil.java
@@ -7,15 +7,16 @@
*
* Contributors:
* Tasktop Technologies - initial API and implementation
- * Sonatype Inc. - transport split
+ * Sonatype, Inc. - transport split and caching support
*******************************************************************************/
package org.eclipse.equinox.internal.p2.discovery.compatibility.util;
import java.io.*;
import java.net.URI;
import java.util.List;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.p2.discovery.compatibility.Activator;
+import org.eclipse.equinox.internal.p2.discovery.compatibility.Messages;
import org.eclipse.equinox.internal.p2.repository.AuthenticationFailedException;
import org.eclipse.equinox.internal.p2.transport.ecf.RepositoryTransport;
@@ -48,13 +49,37 @@ public class TransportUtil {
* the monitor
* @throws IOException
* if a network or IO problem occurs
+ * @throws CoreException
*/
- public static void downloadResource(URI location, File target, IProgressMonitor monitor) throws IOException {
- OutputStream out = new BufferedOutputStream(new FileOutputStream(target));
+ public static void downloadResource(URI location, File target, IProgressMonitor monitor) throws IOException, CoreException {
+ CacheManager cm = Activator.getDefault().getCacheManager();
+ File cacheFile = cm.createCache(location, monitor);
+ if (cacheFile == null) {
+ throw new CoreException(new Status(IStatus.ERROR, Activator.ID, Messages.TransportUtil_InternalError));
+ }
+ copyStream(new BufferedInputStream(new FileInputStream(cacheFile)), true, new BufferedOutputStream(new FileOutputStream(target)), true);
+ }
+
+ public static int copyStream(InputStream in, boolean closeIn, OutputStream out, boolean closeOut) throws IOException {
try {
- new RepositoryTransport().download(location, out, monitor);
+ int written = 0;
+ byte[] buffer = new byte[16 * 1024];
+ int len;
+ while ((len = in.read(buffer)) != -1) {
+ out.write(buffer, 0, len);
+ written += len;
+ }
+ return written;
} finally {
- out.close();
+ try {
+ if (closeIn) {
+ in.close();
+ }
+ } finally {
+ if (closeOut) {
+ out.close();
+ }
+ }
}
}
@@ -72,7 +97,12 @@ public class TransportUtil {
* @throws CoreException
*/
public static void readResource(URI location, TextContentProcessor processor, IProgressMonitor monitor) throws IOException, CoreException {
- InputStream in = new RepositoryTransport().stream(location, monitor);
+ CacheManager cm = Activator.getDefault().getCacheManager();
+ File cacheFile = cm.createCache(location, monitor);
+ if (cacheFile == null) {
+ throw new CoreException(new Status(IStatus.ERROR, Activator.ID, Messages.TransportUtil_InternalError));
+ }
+ InputStream in = new BufferedInputStream(new FileInputStream(cacheFile));
try {
// FIXME how can the charset be determined?
BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8")); //$NON-NLS-1$

Back to the top