/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2008 Sun Microsystems, Inc. * Portions Copyright 2011 ForgeRock AS */ package org.opends.quicksetup.upgrader; import static org.opends.messages.QuickSetupMessages.*; import org.opends.quicksetup.Application; import org.opends.quicksetup.util.Utils; import javax.swing.*; import java.net.URL; import java.net.Proxy; import java.net.URLConnection; import java.net.MalformedURLException; import java.util.*; import java.util.List; import java.util.logging.Logger; import java.util.logging.Level; import java.io.*; import java.awt.*; /** * Manages listing and retrieval of build packages on a remote host. */ public class RemoteBuildManager { static private final Logger LOG = Logger.getLogger(RemoteBuildManager.class.getName()); private Application app; /** * This URL is expected to point at a list of the builds parsable by * the RemoteBuildsPageParser. */ private URL buildListUrl; private Proxy proxy; private String proxyUserName; private char[] proxyPw; /** * Creates an instance. * @param app using this tool * @param url base context for an OpenDJ build list * @param proxy Proxy to use for connections; can be null if not proxy is * to be used */ public RemoteBuildManager(Application app, URL url, Proxy proxy) { this.app = app; this.buildListUrl = url; this.proxy = proxy; } /** * Gets the base context where the build information is stored. * @return URL representing base context of the build repo */ public URL getBaseContext() { return this.buildListUrl; } /** * Gets the list of builds from the build repository using a * progress monitor to keep the user informed about the status * of downloading the build page. * @param in InputStream of build information * @return list of Build objects * @throws IOException if something goes wrong loading the list * from the build repository */ public List listBuilds(InputStream in) throws IOException { String dailyBuildsPage = downloadDailyBuildsPage(in); return Collections.unmodifiableList( RemoteBuildsPageParser.parseBuildList(dailyBuildsPage)); } /** * Gets an input stream to download. * @param c Component parent * @param o Object message to display in the ProgressMonitor * @return InputStream for the build list * @throws IOException if something goes wrong */ public InputStream getDailyBuildsInputStream(final Component c, final Object o) throws IOException { URLConnection conn; if (proxy == null) { conn = buildListUrl.openConnection(); } else { conn = buildListUrl.openConnection(proxy); } String proxyAuthString = createProxyAuthString(); if (proxyAuthString != null) { conn.setRequestProperty("Proxy-Authorization", "Basic " + // DO NOT i18n proxyAuthString); } InputStream in; if (c != null) { ProgressMonitorInputStream pmis = new ProgressMonitorInputStream(c, o, conn.getInputStream()); ProgressMonitor pm = pmis.getProgressMonitor(); pm.setMaximum(conn.getContentLength()); // pm.setMillisToDecideToPopup(0); // pm.setMillisToPopup(0); in = pmis; } else { in = conn.getInputStream(); } return in; } private String downloadDailyBuildsPage(InputStream in) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(in)); StringBuilder builder = new StringBuilder(); String line; while (null != (line = reader.readLine())) { builder.append(line).append('\n'); } return builder.toString(); } /** * Downloads a particular build from the build repository to a specific * location on the local file system. * @param build to download * @param destination directory for the newly downloaded file * @throws IOException if the build could not be downloaded */ public void download(Build build, File destination) throws IOException { download(build.getUrl(), destination); } private void download(URL url, File destination) throws IOException { URLConnection conn = null; if (proxy == null) { conn = url.openConnection(); } else { conn = url.openConnection(proxy); } String proxyAuthString = createProxyAuthString(); if (proxyAuthString != null) { conn.setRequestProperty("Proxy-Authorization", "Basic " + // DO NOT i18n proxyAuthString); } InputStream is = null; FileOutputStream fos = null; // If the destination already exists blow it away, then // create the new file. if (destination.exists()) { if (!destination.delete()) { throw new IOException("Could not overwrite existing file " + Utils.getPath(destination)); } } Utils.createFile(destination); try { is = conn.getInputStream(); int length = conn.getContentLength(); fos = new FileOutputStream(destination); int i = 0; int bytesRead = 0; byte[] buf = new byte[1024]; app.notifyListeners(0, INFO_BUILD_MANAGER_DOWNLOADING_BUILD.get(), null); while ((i = is.read(buf)) != -1) { fos.write(buf, 0, i); if (app != null) { bytesRead += i; if (length > 0) { int progress = (bytesRead * 100) / length; app.notifyListeners(0, INFO_BUILD_MANAGER_DOWNLOADING_BUILD_PROGRESS.get( String.valueOf(progress)), null); } } } app.notifyListeners(0, INFO_BUILD_MANAGER_DOWNLOADING_BUILD_DONE.get(), null); } finally { if (is != null) { is.close(); } if (fos != null) { fos.close(); } } } /** * Sets the proxy object this class will use when establishing network * connections. * @param proxy to use when establishing connections */ public void setProxy(Proxy proxy) { this.proxy = proxy; } /** * Gets the proxy object this class uses when establishing network * connections. * @return Proxy to use when establishing connections */ public Proxy getProxy() { return this.proxy; } /** * Sets the user name this class will use to authenticate to its * proxy when establishing network connections. * @param user this class is acting on behalf of */ public void setProxyUserName(String user) { this.proxyUserName = user; } /** * Sets the user name this class will use to authenticate to its * proxy when establishing network connections. * @return String representing the name of the user of which this class is * acting on behalf */ public String getProxyUserName() { return this.proxyUserName; } /** * Sets the password this class will use to authenticate to its * proxy when establishing network connections. * @param pw char[] representing the password of the user of which this class * is acting on behalf */ public void setProxyPassword(char[] pw) { this.proxyPw = pw; } /** * Sets the password this class will use to authenticate to its * proxy when establishing network connections. * @return char[] representing the password of the user of which this class is * acting on behalf */ public char[] getProxyPassword() { return this.proxyPw; } private String createProxyAuthString() { return createAuthenticationString(getProxyUserName(), getProxyPassword()); } static private String createAuthenticationString(String user, char[] pw) { String s = null; if (user != null && pw != null) { StringBuilder sb = new StringBuilder() .append(user) .append(":") .append(pw); s = org.opends.server.util.Base64.encode(sb.toString().getBytes()); } return s; } /** * Parser for the web page that lists available builds. This pag is expected * to be a tab-delimited text document where each line represents a build with * the following fields: * 1. A build display name (e.g. OpenDS 0.1 Build 036) * 2. A URL where the build's .zip file can be downloaded * 3. A category string for the build (e.g. Weekly Build, Daily Build) */ static private class RemoteBuildsPageParser { /** * Parses a string representing the build information list into a list * of builds sorted by usefulness meaning that release builds are first, * followed by weekly builds and finally daily builds. * @param page String representing the build info page * @return List of Builds */ static public List parseBuildList(String page) { List builds = new ArrayList(); if (page != null) { BufferedReader reader = new BufferedReader(new StringReader(page)); String line; try { while (null != (line = reader.readLine())) { if (!isComment(line)) { try { Build build = parseBuildLine(line); builds.add(build); } catch (IllegalArgumentException iae) { StringBuilder msg = new StringBuilder() .append("Error parsing line '") .append(line) .append("': ") .append(iae.getMessage()); LOG.log(Level.INFO, msg.toString()); } } } } catch (IOException e) { LOG.log(Level.INFO, "error", e); } } else { LOG.log(Level.WARNING, "build list page is null"); } return builds; } static private boolean isComment(String line) { return line != null && line.startsWith("#"); } static private Build parseBuildLine(String line) throws IllegalArgumentException { String displayName = null; String downloadUrlString = null; String categoryString = null; URL downloadUrl; Build.Category category; StringTokenizer st = new StringTokenizer(line, "\t"); if (st.hasMoreTokens()) { displayName = st.nextToken(); } if (st.hasMoreTokens()) { downloadUrlString = st.nextToken(); } if (st.hasMoreTokens()) { categoryString = st.nextToken(); } if (displayName == null || downloadUrlString == null || categoryString == null) { StringBuilder msg = new StringBuilder() .append("Line '") .append(line) .append("' is incomplete or is not correctly delimited") .append("with tab characters"); throw new IllegalArgumentException(msg.toString()); } else { try { downloadUrl = new URL(downloadUrlString); } catch (MalformedURLException e) { StringBuilder msg = new StringBuilder() .append("URL '") .append(downloadUrlString) .append("' is invalid"); throw new IllegalArgumentException(msg.toString()); } category = Build.Category.fromString(categoryString); if (category == null) { StringBuilder msg = new StringBuilder() .append("Category '") .append(categoryString) .append("' is invalid; must be one of "); for (Build.Category c : EnumSet.allOf(Build.Category.class)) { msg.append("'").append(c.getKey()).append("' "); } throw new IllegalArgumentException(msg.toString()); } } return new Build(displayName, downloadUrl, category); } } }