/*
* 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);
}
}
}