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