commit | author | age
|
84f406
|
1 |
/* |
DO |
2 |
* Copyright 2014 gitblit.com. |
|
3 |
* |
|
4 |
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
5 |
* you may not use this file except in compliance with the License. |
|
6 |
* You may obtain a copy of the License at |
|
7 |
* |
|
8 |
* http://www.apache.org/licenses/LICENSE-2.0 |
|
9 |
* |
|
10 |
* Unless required by applicable law or agreed to in writing, software |
|
11 |
* distributed under the License is distributed on an "AS IS" BASIS, |
|
12 |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
13 |
* See the License for the specific language governing permissions and |
|
14 |
* limitations under the License. |
|
15 |
*/ |
|
16 |
package com.gitblit.manager; |
|
17 |
|
e5d0ba
|
18 |
import java.io.BufferedInputStream; |
413e9b
|
19 |
import java.io.File; |
e5d0ba
|
20 |
import java.io.FileFilter; |
b2fec2
|
21 |
import java.io.FileInputStream; |
e5d0ba
|
22 |
import java.io.IOException; |
JM |
23 |
import java.io.InputStream; |
|
24 |
import java.net.HttpURLConnection; |
|
25 |
import java.net.Proxy; |
|
26 |
import java.net.URL; |
|
27 |
import java.net.URLConnection; |
b2fec2
|
28 |
import java.security.DigestInputStream; |
JM |
29 |
import java.security.MessageDigest; |
|
30 |
import java.security.NoSuchAlgorithmException; |
e5d0ba
|
31 |
import java.util.ArrayList; |
b2fec2
|
32 |
import java.util.Iterator; |
e5d0ba
|
33 |
import java.util.List; |
JM |
34 |
import java.util.Map; |
|
35 |
import java.util.TreeMap; |
413e9b
|
36 |
|
84f406
|
37 |
import org.slf4j.Logger; |
DO |
38 |
import org.slf4j.LoggerFactory; |
|
39 |
|
|
40 |
import ro.fortsoft.pf4j.DefaultPluginManager; |
b2fec2
|
41 |
import ro.fortsoft.pf4j.PluginClassLoader; |
JM |
42 |
import ro.fortsoft.pf4j.PluginState; |
|
43 |
import ro.fortsoft.pf4j.PluginStateEvent; |
|
44 |
import ro.fortsoft.pf4j.PluginStateListener; |
413e9b
|
45 |
import ro.fortsoft.pf4j.PluginWrapper; |
027267
|
46 |
import ro.fortsoft.pf4j.Version; |
84f406
|
47 |
|
027267
|
48 |
import com.gitblit.Constants; |
84f406
|
49 |
import com.gitblit.Keys; |
cf4004
|
50 |
import com.gitblit.extensions.GitblitPlugin; |
e5d0ba
|
51 |
import com.gitblit.models.PluginRegistry; |
b2fec2
|
52 |
import com.gitblit.models.PluginRegistry.InstallState; |
e5d0ba
|
53 |
import com.gitblit.models.PluginRegistry.PluginRegistration; |
JM |
54 |
import com.gitblit.models.PluginRegistry.PluginRelease; |
|
55 |
import com.gitblit.utils.Base64; |
413e9b
|
56 |
import com.gitblit.utils.FileUtils; |
e5d0ba
|
57 |
import com.gitblit.utils.JsonUtils; |
JM |
58 |
import com.gitblit.utils.StringUtils; |
|
59 |
import com.google.common.io.Files; |
|
60 |
import com.google.common.io.InputSupplier; |
84f406
|
61 |
|
DO |
62 |
/** |
|
63 |
* The plugin manager maintains the lifecycle of plugins. It is exposed as |
b2fec2
|
64 |
* Dagger bean. The extension consumers supposed to retrieve plugin manager from |
JM |
65 |
* the Dagger DI and retrieve extensions provided by active plugins. |
e5d0ba
|
66 |
* |
84f406
|
67 |
* @author David Ostrovsky |
027267
|
68 |
* @author James Moger |
e5d0ba
|
69 |
* |
84f406
|
70 |
*/ |
b2fec2
|
71 |
public class PluginManager implements IPluginManager, PluginStateListener { |
84f406
|
72 |
|
DO |
73 |
private final Logger logger = LoggerFactory.getLogger(getClass()); |
b2fec2
|
74 |
|
JM |
75 |
private final DefaultPluginManager pf4j; |
e5d0ba
|
76 |
|
413e9b
|
77 |
private final IRuntimeManager runtimeManager; |
e5d0ba
|
78 |
|
JM |
79 |
// timeout defaults of Maven 3.0.4 in seconds |
|
80 |
private int connectTimeout = 20; |
|
81 |
|
|
82 |
private int readTimeout = 12800; |
84f406
|
83 |
|
DO |
84 |
public PluginManager(IRuntimeManager runtimeManager) { |
b2fec2
|
85 |
File dir = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins"); |
ec53f7
|
86 |
dir.mkdirs(); |
413e9b
|
87 |
this.runtimeManager = runtimeManager; |
027267
|
88 |
|
b2fec2
|
89 |
this.pf4j = new DefaultPluginManager(dir); |
027267
|
90 |
|
JM |
91 |
try { |
|
92 |
Version systemVersion = Version.createVersion(Constants.getVersion()); |
|
93 |
pf4j.setSystemVersion(systemVersion); |
|
94 |
} catch (Exception e) { |
|
95 |
logger.error(null, e); |
|
96 |
} |
|
97 |
} |
|
98 |
|
|
99 |
@Override |
|
100 |
public Version getSystemVersion() { |
|
101 |
return pf4j.getSystemVersion(); |
b2fec2
|
102 |
} |
JM |
103 |
|
|
104 |
@Override |
|
105 |
public void pluginStateChanged(PluginStateEvent event) { |
|
106 |
logger.debug(event.toString()); |
84f406
|
107 |
} |
DO |
108 |
|
|
109 |
@Override |
|
110 |
public PluginManager start() { |
b2fec2
|
111 |
pf4j.loadPlugins(); |
JM |
112 |
logger.debug("Starting plugins"); |
|
113 |
pf4j.startPlugins(); |
84f406
|
114 |
return this; |
DO |
115 |
} |
|
116 |
|
|
117 |
@Override |
|
118 |
public PluginManager stop() { |
b2fec2
|
119 |
logger.debug("Stopping plugins"); |
JM |
120 |
pf4j.stopPlugins(); |
84f406
|
121 |
return null; |
DO |
122 |
} |
e5d0ba
|
123 |
|
b2fec2
|
124 |
/** |
JM |
125 |
* Installs the plugin from the url. |
|
126 |
* |
|
127 |
* @param url |
|
128 |
* @param verifyChecksum |
|
129 |
* @return true if successful |
|
130 |
*/ |
413e9b
|
131 |
@Override |
b2fec2
|
132 |
public synchronized boolean installPlugin(String url, boolean verifyChecksum) throws IOException { |
JM |
133 |
File file = download(url, verifyChecksum); |
|
134 |
if (file == null || !file.exists()) { |
|
135 |
logger.error("Failed to download plugin {}", url); |
|
136 |
return false; |
|
137 |
} |
e5d0ba
|
138 |
|
b2fec2
|
139 |
String pluginId = pf4j.loadPlugin(file); |
JM |
140 |
if (StringUtils.isEmpty(pluginId)) { |
|
141 |
logger.error("Failed to load plugin {}", file); |
|
142 |
return false; |
413e9b
|
143 |
} |
b2fec2
|
144 |
|
cf4004
|
145 |
// allow the plugin to prepare for operation after installation |
JM |
146 |
PluginWrapper pluginWrapper = pf4j.getPlugin(pluginId); |
|
147 |
if (pluginWrapper.getPlugin() instanceof GitblitPlugin) { |
|
148 |
((GitblitPlugin) pluginWrapper.getPlugin()).onInstall(); |
|
149 |
} |
|
150 |
|
b2fec2
|
151 |
PluginState state = pf4j.startPlugin(pluginId); |
JM |
152 |
return PluginState.STARTED.equals(state); |
413e9b
|
153 |
} |
e5d0ba
|
154 |
|
027267
|
155 |
@Override |
74f3d9
|
156 |
public synchronized boolean upgradePlugin(String pluginId, String url, boolean verifyChecksum) throws IOException { |
JM |
157 |
// ensure we can download the update BEFORE we remove the existing one |
|
158 |
File file = download(url, verifyChecksum); |
|
159 |
if (file == null || !file.exists()) { |
|
160 |
logger.error("Failed to download plugin {}", url); |
|
161 |
return false; |
|
162 |
} |
|
163 |
|
cf4004
|
164 |
Version oldVersion = pf4j.getPlugin(pluginId).getDescriptor().getVersion(); |
JM |
165 |
if (removePlugin(pluginId, false)) { |
74f3d9
|
166 |
String newPluginId = pf4j.loadPlugin(file); |
JM |
167 |
if (StringUtils.isEmpty(newPluginId)) { |
|
168 |
logger.error("Failed to load plugin {}", file); |
|
169 |
return false; |
cf4004
|
170 |
} |
JM |
171 |
|
|
172 |
// the plugin to handle an upgrade |
|
173 |
PluginWrapper pluginWrapper = pf4j.getPlugin(newPluginId); |
|
174 |
if (pluginWrapper.getPlugin() instanceof GitblitPlugin) { |
|
175 |
((GitblitPlugin) pluginWrapper.getPlugin()).onUpgrade(oldVersion); |
74f3d9
|
176 |
} |
JM |
177 |
|
|
178 |
PluginState state = pf4j.startPlugin(newPluginId); |
|
179 |
return PluginState.STARTED.equals(state); |
|
180 |
} else { |
|
181 |
logger.error("Failed to delete plugin {}", pluginId); |
|
182 |
} |
|
183 |
return false; |
|
184 |
} |
|
185 |
|
e5d0ba
|
186 |
@Override |
b2fec2
|
187 |
public synchronized boolean disablePlugin(String pluginId) { |
JM |
188 |
return pf4j.disablePlugin(pluginId); |
|
189 |
} |
|
190 |
|
|
191 |
@Override |
|
192 |
public synchronized boolean enablePlugin(String pluginId) { |
|
193 |
if (pf4j.enablePlugin(pluginId)) { |
|
194 |
return PluginState.STARTED == pf4j.startPlugin(pluginId); |
|
195 |
} |
|
196 |
return false; |
|
197 |
} |
|
198 |
|
|
199 |
@Override |
cf4004
|
200 |
public synchronized boolean uninstallPlugin(String pluginId) { |
JM |
201 |
return removePlugin(pluginId, true); |
|
202 |
} |
|
203 |
|
|
204 |
protected boolean removePlugin(String pluginId, boolean isUninstall) { |
b2fec2
|
205 |
PluginWrapper pluginWrapper = getPlugin(pluginId); |
JM |
206 |
final String name = pluginWrapper.getPluginPath().substring(1); |
cf4004
|
207 |
|
JM |
208 |
if (isUninstall) { |
|
209 |
// allow the plugin to prepare for uninstallation |
|
210 |
if (pluginWrapper.getPlugin() instanceof GitblitPlugin) { |
|
211 |
((GitblitPlugin) pluginWrapper.getPlugin()).onUninstall(); |
|
212 |
} |
|
213 |
} |
|
214 |
|
b2fec2
|
215 |
if (pf4j.deletePlugin(pluginId)) { |
JM |
216 |
|
|
217 |
// delete the checksums |
|
218 |
File pFolder = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins"); |
|
219 |
File [] checksums = pFolder.listFiles(new FileFilter() { |
|
220 |
@Override |
|
221 |
public boolean accept(File file) { |
|
222 |
if (!file.isFile()) { |
|
223 |
return false; |
|
224 |
} |
|
225 |
|
|
226 |
return file.getName().startsWith(name) && |
|
227 |
(file.getName().toLowerCase().endsWith(".sha1") |
|
228 |
|| file.getName().toLowerCase().endsWith(".md5")); |
|
229 |
} |
|
230 |
}); |
|
231 |
|
|
232 |
if (checksums != null) { |
|
233 |
for (File checksum : checksums) { |
|
234 |
checksum.delete(); |
|
235 |
} |
|
236 |
} |
|
237 |
return true; |
|
238 |
} |
|
239 |
return false; |
|
240 |
} |
|
241 |
|
|
242 |
@Override |
|
243 |
public synchronized PluginState startPlugin(String pluginId) { |
|
244 |
return pf4j.startPlugin(pluginId); |
|
245 |
} |
|
246 |
|
|
247 |
@Override |
|
248 |
public synchronized PluginState stopPlugin(String pluginId) { |
|
249 |
return pf4j.stopPlugin(pluginId); |
|
250 |
} |
|
251 |
|
|
252 |
@Override |
|
253 |
public synchronized void startPlugins() { |
|
254 |
pf4j.startPlugins(); |
|
255 |
} |
|
256 |
|
|
257 |
@Override |
|
258 |
public synchronized void stopPlugins() { |
|
259 |
pf4j.stopPlugins(); |
|
260 |
} |
|
261 |
|
|
262 |
@Override |
|
263 |
public synchronized List<PluginWrapper> getPlugins() { |
|
264 |
return pf4j.getPlugins(); |
|
265 |
} |
|
266 |
|
|
267 |
@Override |
|
268 |
public synchronized PluginWrapper getPlugin(String pluginId) { |
|
269 |
return pf4j.getPlugin(pluginId); |
|
270 |
} |
|
271 |
|
|
272 |
@Override |
|
273 |
public synchronized List<Class<?>> getExtensionClasses(String pluginId) { |
|
274 |
List<Class<?>> list = new ArrayList<Class<?>>(); |
|
275 |
PluginClassLoader loader = pf4j.getPluginClassLoader(pluginId); |
|
276 |
for (String className : pf4j.getExtensionClassNames(pluginId)) { |
|
277 |
try { |
|
278 |
list.add(loader.loadClass(className)); |
|
279 |
} catch (ClassNotFoundException e) { |
|
280 |
logger.error(String.format("Failed to find %s in %s", className, pluginId), e); |
|
281 |
} |
|
282 |
} |
|
283 |
return list; |
|
284 |
} |
|
285 |
|
|
286 |
@Override |
|
287 |
public synchronized <T> List<T> getExtensions(Class<T> type) { |
|
288 |
return pf4j.getExtensions(type); |
|
289 |
} |
|
290 |
|
|
291 |
@Override |
|
292 |
public synchronized PluginWrapper whichPlugin(Class<?> clazz) { |
|
293 |
return pf4j.whichPlugin(clazz); |
|
294 |
} |
|
295 |
|
|
296 |
@Override |
ec53f7
|
297 |
public synchronized boolean refreshRegistry(boolean verifyChecksum) { |
e5d0ba
|
298 |
String dr = "http://gitblit.github.io/gitblit-registry/plugins.json"; |
JM |
299 |
String url = runtimeManager.getSettings().getString(Keys.plugins.registry, dr); |
|
300 |
try { |
ec53f7
|
301 |
File file = download(url, verifyChecksum); |
b2fec2
|
302 |
if (file != null && file.exists()) { |
JM |
303 |
URL selfUrl = new URL(url.substring(0, url.lastIndexOf('/'))); |
|
304 |
// replace ${self} with the registry url |
|
305 |
String content = FileUtils.readContent(file, "\n"); |
|
306 |
content = content.replace("${self}", selfUrl.toString()); |
|
307 |
FileUtils.writeContent(file, content); |
|
308 |
} |
e5d0ba
|
309 |
} catch (Exception e) { |
JM |
310 |
logger.error(String.format("Failed to retrieve plugins.json from %s", url), e); |
|
311 |
} |
|
312 |
return false; |
|
313 |
} |
|
314 |
|
|
315 |
protected List<PluginRegistry> getRegistries() { |
|
316 |
List<PluginRegistry> list = new ArrayList<PluginRegistry>(); |
|
317 |
File folder = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins"); |
|
318 |
FileFilter jsonFilter = new FileFilter() { |
|
319 |
@Override |
|
320 |
public boolean accept(File file) { |
|
321 |
return !file.isDirectory() && file.getName().toLowerCase().endsWith(".json"); |
|
322 |
} |
|
323 |
}; |
|
324 |
|
b2fec2
|
325 |
File[] files = folder.listFiles(jsonFilter); |
e5d0ba
|
326 |
if (files == null || files.length == 0) { |
JM |
327 |
// automatically retrieve the registry if we don't have a local copy |
ec53f7
|
328 |
refreshRegistry(true); |
e5d0ba
|
329 |
files = folder.listFiles(jsonFilter); |
JM |
330 |
} |
|
331 |
|
|
332 |
if (files == null || files.length == 0) { |
|
333 |
return list; |
|
334 |
} |
|
335 |
|
|
336 |
for (File file : files) { |
|
337 |
PluginRegistry registry = null; |
|
338 |
try { |
|
339 |
String json = FileUtils.readContent(file, "\n"); |
|
340 |
registry = JsonUtils.fromJsonString(json, PluginRegistry.class); |
b2fec2
|
341 |
registry.setup(); |
e5d0ba
|
342 |
} catch (Exception e) { |
JM |
343 |
logger.error("Failed to deserialize " + file, e); |
|
344 |
} |
|
345 |
if (registry != null) { |
|
346 |
list.add(registry); |
|
347 |
} |
|
348 |
} |
|
349 |
return list; |
|
350 |
} |
|
351 |
|
|
352 |
@Override |
b2fec2
|
353 |
public synchronized List<PluginRegistration> getRegisteredPlugins() { |
e5d0ba
|
354 |
List<PluginRegistration> list = new ArrayList<PluginRegistration>(); |
JM |
355 |
Map<String, PluginRegistration> map = new TreeMap<String, PluginRegistration>(); |
|
356 |
for (PluginRegistry registry : getRegistries()) { |
b2fec2
|
357 |
list.addAll(registry.registrations); |
JM |
358 |
for (PluginRegistration reg : list) { |
e5d0ba
|
359 |
reg.installedRelease = null; |
JM |
360 |
map.put(reg.id, reg); |
|
361 |
} |
|
362 |
} |
74f3d9
|
363 |
|
b2fec2
|
364 |
for (PluginWrapper pw : pf4j.getPlugins()) { |
e5d0ba
|
365 |
String id = pw.getDescriptor().getPluginId(); |
027267
|
366 |
Version pv = pw.getDescriptor().getVersion(); |
e5d0ba
|
367 |
PluginRegistration reg = map.get(id); |
JM |
368 |
if (reg != null) { |
|
369 |
reg.installedRelease = pv.toString(); |
|
370 |
} |
|
371 |
} |
|
372 |
return list; |
|
373 |
} |
|
374 |
|
|
375 |
@Override |
b2fec2
|
376 |
public synchronized List<PluginRegistration> getRegisteredPlugins(InstallState state) { |
JM |
377 |
List<PluginRegistration> list = getRegisteredPlugins(); |
|
378 |
Iterator<PluginRegistration> itr = list.iterator(); |
|
379 |
while (itr.hasNext()) { |
67278f
|
380 |
if (state != itr.next().getInstallState(getSystemVersion())) { |
b2fec2
|
381 |
itr.remove(); |
JM |
382 |
} |
|
383 |
} |
|
384 |
return list; |
|
385 |
} |
|
386 |
|
|
387 |
@Override |
fad6b4
|
388 |
public synchronized PluginRegistration lookupPlugin(String pluginId) { |
b2fec2
|
389 |
for (PluginRegistration reg : getRegisteredPlugins()) { |
fad6b4
|
390 |
if (reg.id.equalsIgnoreCase(pluginId)) { |
e5d0ba
|
391 |
return reg; |
JM |
392 |
} |
|
393 |
} |
|
394 |
return null; |
|
395 |
} |
|
396 |
|
|
397 |
@Override |
fad6b4
|
398 |
public synchronized PluginRelease lookupRelease(String pluginId, String version) { |
JM |
399 |
PluginRegistration reg = lookupPlugin(pluginId); |
b2fec2
|
400 |
if (reg == null) { |
JM |
401 |
return null; |
e5d0ba
|
402 |
} |
JM |
403 |
|
b2fec2
|
404 |
PluginRelease pv; |
JM |
405 |
if (StringUtils.isEmpty(version)) { |
80cf76
|
406 |
pv = reg.getCurrentRelease(getSystemVersion()); |
b2fec2
|
407 |
} else { |
JM |
408 |
pv = reg.getRelease(version); |
|
409 |
} |
|
410 |
return pv; |
e5d0ba
|
411 |
} |
JM |
412 |
|
|
413 |
/** |
b2fec2
|
414 |
* Downloads a file with optional checksum verification. |
e5d0ba
|
415 |
* |
JM |
416 |
* @param url |
b2fec2
|
417 |
* @param verifyChecksum |
JM |
418 |
* @return |
|
419 |
* @throws IOException |
e5d0ba
|
420 |
*/ |
b2fec2
|
421 |
protected File download(String url, boolean verifyChecksum) throws IOException { |
JM |
422 |
File file = downloadFile(url); |
|
423 |
|
6ec71b
|
424 |
if (!verifyChecksum) { |
JM |
425 |
return file; |
|
426 |
} |
|
427 |
|
b2fec2
|
428 |
File sha1File = null; |
e5d0ba
|
429 |
try { |
b2fec2
|
430 |
sha1File = downloadFile(url + ".sha1"); |
e5d0ba
|
431 |
} catch (IOException e) { |
JM |
432 |
} |
b2fec2
|
433 |
|
JM |
434 |
File md5File = null; |
|
435 |
try { |
|
436 |
md5File = downloadFile(url + ".md5"); |
|
437 |
} catch (IOException e) { |
|
438 |
|
|
439 |
} |
|
440 |
|
|
441 |
if (sha1File == null && md5File == null && verifyChecksum) { |
|
442 |
throw new IOException("Missing SHA1 and MD5 checksums for " + url); |
|
443 |
} |
|
444 |
|
|
445 |
String expected; |
|
446 |
MessageDigest md = null; |
|
447 |
if (sha1File != null && sha1File.exists()) { |
|
448 |
// prefer SHA1 to MD5 |
|
449 |
expected = FileUtils.readContent(sha1File, "\n").split(" ")[0].trim(); |
|
450 |
try { |
|
451 |
md = MessageDigest.getInstance("SHA-1"); |
|
452 |
} catch (NoSuchAlgorithmException e) { |
|
453 |
logger.error(null, e); |
|
454 |
} |
|
455 |
} else { |
|
456 |
expected = FileUtils.readContent(md5File, "\n").split(" ")[0].trim(); |
|
457 |
try { |
|
458 |
md = MessageDigest.getInstance("MD5"); |
|
459 |
} catch (Exception e) { |
|
460 |
logger.error(null, e); |
|
461 |
} |
|
462 |
} |
|
463 |
|
|
464 |
// calculate the checksum |
|
465 |
FileInputStream is = null; |
|
466 |
try { |
|
467 |
is = new FileInputStream(file); |
|
468 |
DigestInputStream dis = new DigestInputStream(is, md); |
|
469 |
byte [] buffer = new byte[1024]; |
|
470 |
while ((dis.read(buffer)) > -1) { |
|
471 |
// read |
|
472 |
} |
|
473 |
dis.close(); |
|
474 |
|
|
475 |
byte [] digest = md.digest(); |
|
476 |
String calculated = StringUtils.toHex(digest).trim(); |
|
477 |
|
|
478 |
if (!expected.equals(calculated)) { |
|
479 |
String msg = String.format("Invalid checksum for %s\nAlgorithm: %s\nExpected: %s\nCalculated: %s", |
|
480 |
file.getAbsolutePath(), |
|
481 |
md.getAlgorithm(), |
|
482 |
expected, |
|
483 |
calculated); |
|
484 |
file.delete(); |
|
485 |
throw new IOException(msg); |
|
486 |
} |
|
487 |
} finally { |
|
488 |
if (is != null) { |
|
489 |
is.close(); |
|
490 |
} |
|
491 |
} |
|
492 |
return file; |
e5d0ba
|
493 |
} |
JM |
494 |
|
|
495 |
/** |
|
496 |
* Download a file to the plugins folder. |
|
497 |
* |
|
498 |
* @param url |
b2fec2
|
499 |
* @return the downloaded file |
e5d0ba
|
500 |
* @throws IOException |
JM |
501 |
*/ |
b2fec2
|
502 |
protected File downloadFile(String url) throws IOException { |
e5d0ba
|
503 |
File pFolder = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins"); |
f7e977
|
504 |
pFolder.mkdirs(); |
e5d0ba
|
505 |
File tmpFile = new File(pFolder, StringUtils.getSHA1(url) + ".tmp"); |
JM |
506 |
if (tmpFile.exists()) { |
|
507 |
tmpFile.delete(); |
|
508 |
} |
|
509 |
|
|
510 |
URL u = new URL(url); |
|
511 |
final URLConnection conn = getConnection(u); |
|
512 |
|
|
513 |
// try to get the server-specified last-modified date of this artifact |
|
514 |
long lastModified = conn.getHeaderFieldDate("Last-Modified", System.currentTimeMillis()); |
|
515 |
|
|
516 |
Files.copy(new InputSupplier<InputStream>() { |
b2fec2
|
517 |
@Override |
e5d0ba
|
518 |
public InputStream getInput() throws IOException { |
b2fec2
|
519 |
return new BufferedInputStream(conn.getInputStream()); |
e5d0ba
|
520 |
} |
JM |
521 |
}, tmpFile); |
|
522 |
|
|
523 |
File destFile = new File(pFolder, StringUtils.getLastPathElement(u.getPath())); |
|
524 |
if (destFile.exists()) { |
|
525 |
destFile.delete(); |
|
526 |
} |
|
527 |
tmpFile.renameTo(destFile); |
|
528 |
destFile.setLastModified(lastModified); |
|
529 |
|
b2fec2
|
530 |
return destFile; |
e5d0ba
|
531 |
} |
JM |
532 |
|
|
533 |
protected URLConnection getConnection(URL url) throws IOException { |
|
534 |
java.net.Proxy proxy = getProxy(url); |
|
535 |
HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy); |
|
536 |
if (java.net.Proxy.Type.DIRECT != proxy.type()) { |
|
537 |
String auth = getProxyAuthorization(url); |
|
538 |
conn.setRequestProperty("Proxy-Authorization", auth); |
|
539 |
} |
|
540 |
|
|
541 |
String username = null; |
|
542 |
String password = null; |
|
543 |
if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) { |
|
544 |
// set basic authentication header |
|
545 |
String auth = Base64.encodeBytes((username + ":" + password).getBytes()); |
|
546 |
conn.setRequestProperty("Authorization", "Basic " + auth); |
|
547 |
} |
|
548 |
|
|
549 |
// configure timeouts |
|
550 |
conn.setConnectTimeout(connectTimeout * 1000); |
|
551 |
conn.setReadTimeout(readTimeout * 1000); |
|
552 |
|
|
553 |
switch (conn.getResponseCode()) { |
|
554 |
case HttpURLConnection.HTTP_MOVED_TEMP: |
|
555 |
case HttpURLConnection.HTTP_MOVED_PERM: |
|
556 |
// handle redirects by closing this connection and opening a new |
|
557 |
// one to the new location of the requested resource |
|
558 |
String newLocation = conn.getHeaderField("Location"); |
|
559 |
if (!StringUtils.isEmpty(newLocation)) { |
|
560 |
logger.info("following redirect to {0}", newLocation); |
|
561 |
conn.disconnect(); |
|
562 |
return getConnection(new URL(newLocation)); |
|
563 |
} |
|
564 |
} |
|
565 |
|
|
566 |
return conn; |
|
567 |
} |
|
568 |
|
|
569 |
protected Proxy getProxy(URL url) { |
|
570 |
return java.net.Proxy.NO_PROXY; |
|
571 |
} |
|
572 |
|
|
573 |
protected String getProxyAuthorization(URL url) { |
|
574 |
return ""; |
|
575 |
} |
84f406
|
576 |
} |