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 }