001/*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * Sonar is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019 */
020package org.sonar.batch.bootstrapper;
021
022import java.io.*;
023import java.net.HttpURLConnection;
024import java.net.MalformedURLException;
025import java.net.URL;
026import java.util.ArrayList;
027import java.util.List;
028
029public class Bootstrapper {
030
031  private static final String VERSION_PATH = "/api/server/version";
032  private static final String BATCH_PATH = "/batch/";
033
034  public static final int CONNECT_TIMEOUT_MILLISECONDS = 30000;
035  public static final int READ_TIMEOUT_MILLISECONDS = 60000;
036
037  private File bootDir;
038  private String serverUrl;
039  private String productToken;
040  private String serverVersion;
041
042  /**
043   * @param productToken part of User-Agent request-header field - see http://tools.ietf.org/html/rfc1945#section-10.15
044   */
045  public Bootstrapper(String productToken, String serverUrl, File workDir) {
046    this.productToken = productToken;
047    bootDir = new File(workDir, "batch");
048    bootDir.mkdirs();
049    if (serverUrl.endsWith("/")) {
050      this.serverUrl = serverUrl.substring(0, serverUrl.length() - 1);
051    } else {
052      this.serverUrl = serverUrl;
053    }
054  }
055
056  /**
057   * @return server url
058   */
059  public String getServerUrl() {
060    return serverUrl;
061  }
062
063  /**
064   * @return server version
065   */
066  public String getServerVersion() {
067    if (serverVersion == null) {
068      try {
069        serverVersion = remoteContent(VERSION_PATH);
070      } catch (IOException e) {
071        throw new BootstrapException(e.getMessage(), e);
072      }
073    }
074    return serverVersion;
075  }
076
077  /**
078   * Download batch files from server and creates {@link BootstrapClassLoader}.
079   * To use this method version of Sonar should be at least 2.6.
080   * 
081   * @param urls additional URLs for loading classes and resources
082   * @param parent parent ClassLoader
083   * @param unmaskedPackages only classes and resources from those packages would be available for loading from parent
084   */
085  public BootstrapClassLoader createClassLoader(URL[] urls, ClassLoader parent, String... unmaskedPackages) {
086    BootstrapClassLoader classLoader = new BootstrapClassLoader(parent, unmaskedPackages);
087    List<File> files = downloadBatchFiles();
088    for (URL url : urls) {
089      classLoader.addURL(url);
090    }
091    for (File file : files) {
092      try {
093        classLoader.addURL(file.toURI().toURL());
094      } catch (MalformedURLException e) {
095        throw new BootstrapException(e);
096      }
097    }
098    return classLoader;
099  }
100
101  private void remoteContentToFile(String path, File toFile) {
102    InputStream input = null;
103    FileOutputStream output = null;
104    String fullUrl = serverUrl + path;
105    try {
106      HttpURLConnection connection = newHttpConnection(new URL(fullUrl));
107      output = new FileOutputStream(toFile, false);
108      input = connection.getInputStream();
109      BootstrapperIOUtils.copyLarge(input, output);
110    } catch (IOException e) {
111      BootstrapperIOUtils.closeQuietly(output);
112      BootstrapperIOUtils.deleteFileQuietly(toFile);
113      throw new BootstrapException("Fail to download the file: " + fullUrl, e);
114    } finally {
115      BootstrapperIOUtils.closeQuietly(input);
116      BootstrapperIOUtils.closeQuietly(output);
117    }
118  }
119
120  String remoteContent(String path) throws IOException {
121    String fullUrl = serverUrl + path;
122    HttpURLConnection conn = newHttpConnection(new URL(fullUrl));
123    Reader reader = new InputStreamReader((InputStream) conn.getContent());
124    try {
125      int statusCode = conn.getResponseCode();
126      if (statusCode != HttpURLConnection.HTTP_OK) {
127        throw new IOException("Status returned by url : '" + fullUrl + "' is invalid : " + statusCode);
128      }
129      return BootstrapperIOUtils.toString(reader);
130    } finally {
131      BootstrapperIOUtils.closeQuietly(reader);
132      conn.disconnect();
133    }
134  }
135
136  /**
137   * By convention, the product tokens are listed in order of their significance for identifying the application.
138   */
139  String getUserAgent() {
140    return "sonar-bootstrapper/" + BootstrapperVersion.getVersion() + " " + productToken;
141  }
142
143  HttpURLConnection newHttpConnection(URL url) throws IOException {
144    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
145    connection.setConnectTimeout(CONNECT_TIMEOUT_MILLISECONDS);
146    connection.setReadTimeout(READ_TIMEOUT_MILLISECONDS);
147    connection.setInstanceFollowRedirects(true);
148    connection.setRequestMethod("GET");
149    connection.setRequestProperty("User-Agent", getUserAgent());
150    return connection;
151  }
152
153  private List<File> downloadBatchFiles() {
154    try {
155      List<File> files = new ArrayList<File>();
156      String libs = remoteContent(BATCH_PATH);
157      for (String lib : libs.split(",")) {
158        File file = new File(bootDir, lib);
159        remoteContentToFile(BATCH_PATH + lib, file);
160        files.add(file);
161      }
162      return files;
163    } catch (Exception e) {
164      throw new BootstrapException(e);
165    }
166  }
167}