001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2011 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     */
020    package org.sonar.batch.bootstrapper;
021    
022    import java.io.*;
023    import java.net.HttpURLConnection;
024    import java.net.MalformedURLException;
025    import java.net.URL;
026    import java.util.ArrayList;
027    import java.util.List;
028    
029    public 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    }