001/** 002 * Copyright (C) 2006-2025 Talend Inc. - www.talend.com 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.talend.sdk.component.tools.exec; 017 018import static java.util.Locale.ROOT; 019import static java.util.stream.Collectors.joining; 020 021import java.io.BufferedInputStream; 022import java.io.BufferedOutputStream; 023import java.io.File; 024import java.io.FileInputStream; 025import java.io.FileNotFoundException; 026import java.io.FileOutputStream; 027import java.io.FileWriter; 028import java.io.IOException; 029import java.io.InputStream; 030import java.io.OutputStream; 031import java.io.UnsupportedEncodingException; 032import java.io.Writer; 033import java.net.HttpURLConnection; 034import java.net.URL; 035import java.net.URLDecoder; 036import java.nio.file.Files; 037import java.nio.file.StandardCopyOption; 038import java.time.LocalDateTime; 039import java.time.format.DateTimeFormatter; 040import java.util.ArrayList; 041import java.util.Base64; 042import java.util.Enumeration; 043import java.util.List; 044import java.util.Objects; 045import java.util.Properties; 046import java.util.UUID; 047import java.util.concurrent.CountDownLatch; 048import java.util.concurrent.ExecutorService; 049import java.util.concurrent.Executors; 050import java.util.concurrent.TimeUnit; 051import java.util.jar.JarEntry; 052import java.util.jar.JarFile; 053import java.util.jar.JarInputStream; 054import java.util.stream.Collectors; 055import java.util.stream.Stream; 056 057import javax.xml.parsers.DocumentBuilder; 058import javax.xml.parsers.DocumentBuilderFactory; 059import javax.xml.parsers.ParserConfigurationException; 060 061import org.w3c.dom.Document; 062import org.w3c.dom.Node; 063import org.w3c.dom.NodeList; 064import org.xml.sax.SAXException; 065 066// IMPORTANT: this class MUST not have ANY dependency, not even slf4j! 067public class CarMain { 068 069 public static final String COMPONENT_JAVA_COORDINATES = "component.java.coordinates"; 070 071 public static final String COMPONENT_COORDINATES = "component_coordinates"; 072 073 public static final String COMPONENT_SERVER_EXTENSIONS = "component.server.extensions"; 074 075 public static final String UTF_8_ENC = "UTF-8"; 076 077 private CarMain() { 078 // no-op 079 } 080 081 public static void main(final String[] args) { 082 if (args == null || args.length < 2) { 083 help(); 084 return; 085 } 086 if (Stream.of(args).anyMatch(Objects::isNull)) { 087 throw new IllegalArgumentException("No argument can be null"); 088 } 089 boolean forceOverwrite = false; 090 for (String arg : args) { 091 if ("-f".equals(arg)) { 092 forceOverwrite = true; 093 break; 094 } 095 } 096 097 switch (args[0].toLowerCase(ROOT)) { 098 case "studio-deploy": 099 final String studioPath; 100 if (args.length == 2) { 101 studioPath = args[1]; 102 } else { 103 studioPath = getArgument("--location", args); 104 } 105 if (studioPath == null || studioPath.isEmpty()) { 106 System.err.println("Path to studio is not set. Use '--location <location>' to set it."); 107 help(); 108 break; 109 } 110 deployInStudio(studioPath, forceOverwrite); 111 break; 112 case "maven-deploy": 113 final String mavenPath; 114 if (args.length == 2) { 115 mavenPath = args[1]; 116 } else { 117 mavenPath = getArgument("--location", args); 118 } 119 if (mavenPath == null || mavenPath.isEmpty()) { 120 System.err.println("Path to maven repository is not set. Use '--location <location>' to set it."); 121 help(); 122 break; 123 } 124 deployInM2(mavenPath, forceOverwrite); 125 break; 126 case "deploy-to-nexus": 127 final String url = getArgument("--url", args); 128 final String repo = getArgument("--repo", args); 129 final String user = getArgument("--user", args); 130 final String pass = getArgument("--pass", args); 131 final String threads = getArgument("--threads", args); 132 final int threadsNum; 133 if (threads == null) { 134 threadsNum = Runtime.getRuntime().availableProcessors(); 135 } else { 136 threadsNum = Integer.parseInt(threads); 137 } 138 final String dir = getArgument("--dir", args); 139 if (url == null || url.isEmpty()) { 140 System.err.println("Nexus url is not set. Use '--url <url>' to set it"); 141 help(); 142 break; 143 } 144 if (repo == null || repo.isEmpty()) { 145 System.err.println("Nexus repo is not set. Use '--repo <repository>' to set it"); 146 help(); 147 break; 148 } 149 deployToNexus(url, repo, user, pass, threadsNum, dir); 150 break; 151 default: 152 help(); 153 throw new IllegalArgumentException("Unknown command '" + args[0] + "'"); 154 } 155 } 156 157 private static String getArgument(final String argumentPrefix, final String... args) { 158 for (int i = 1; i < args.length - 1; i++) { 159 if (args[i].equals(argumentPrefix)) { 160 return args[i + 1]; 161 } 162 } 163 return null; 164 } 165 166 private static void deployInM2(final String m2, final boolean forceOverwrite) { 167 final File m2File = new File(m2); 168 if (!m2File.exists()) { 169 throw new IllegalArgumentException(m2 + " doesn't exist"); 170 } 171 final String component = installJars(m2File, forceOverwrite).getProperty(COMPONENT_COORDINATES); 172 System.out 173 .println("Installed " + jarLocation(CarMain.class).getName() + " in " + m2 + ", " 174 + "you can now register '" + component + "' component in your application."); 175 } 176 177 private static void deployInStudio(final String studioLocation, final boolean forceOverwrite) { 178 System.out.println(String.format("Connector is being deployed to %s.", studioLocation)); 179 final File root = new File(studioLocation); 180 if (!root.isDirectory()) { 181 throw new IllegalArgumentException(studioLocation + " is not a valid directory"); 182 } 183 184 final File config = new File(studioLocation, "configuration/config.ini"); 185 if (!config.exists()) { 186 throw new IllegalArgumentException("No " + config + " found, is your studio location right?"); 187 } 188 189 final Properties configuration = readProperties(config); 190 final File m2Root; 191 String m2RepoPath = System.getProperty("talend.studio.m2.repo", null); 192 if (m2RepoPath != null) { 193 m2Root = new File(m2RepoPath); 194 } else { 195 final String repositoryType = configuration.getProperty("maven.repository"); 196 if ("global".equals(repositoryType)) { 197 // grab local maven setup, we use talend env first to override dev one then dev env setup 198 // and finally this main system props as a more natural config but less likely set on a dev machine 199 m2Root = Stream 200 .of("TALEND_STUDIO_MAVEN_HOME", "MAVEN_HOME", "M2_HOME", 201 "talend.component.server.maven.repository", 202 "talend.studio.m2") 203 .map(it -> it.contains(".") ? System.getProperty(it) : System.getenv(it)) 204 .filter(Objects::nonNull) 205 .findFirst() 206 .map(mvnHome -> { 207 final File globalSettings = new File(mvnHome, "conf/settings.xml"); 208 if (globalSettings.exists()) { 209 try { 210 final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 211 factory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true); 212 factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 213 final DocumentBuilder builder = factory.newDocumentBuilder(); 214 final Document document = builder.parse(globalSettings); 215 final NodeList localRepository = document.getElementsByTagName("localRepository"); 216 if (localRepository.getLength() == 1) { 217 final Node node = localRepository.item(0); 218 if (node != null) { 219 final String repoPath = node.getTextContent(); 220 if (repoPath != null) { 221 return new File(repoPath); 222 } 223 } 224 } 225 } catch (final ParserConfigurationException | SAXException | IOException e) { 226 throw new IllegalStateException(e); 227 } 228 } 229 return null; 230 }) 231 .orElseGet(() -> new File(System.getProperty("user.home"), ".m2/repository/")); 232 } else { 233 m2Root = new File(studioLocation, "configuration/.m2/repository/"); 234 } 235 } 236 if (!m2Root.isDirectory()) { 237 throw new IllegalArgumentException(m2Root + " does not exist, did you specify a valid m2 studio property?"); 238 } 239 240 // install jars 241 final Properties carProperties = installJars(m2Root, forceOverwrite); 242 final String mainGav = carProperties.getProperty(COMPONENT_COORDINATES); 243 final String type = carProperties.getProperty("type", "connector"); 244 245 // register the component/extension 246 final String key = 247 "extension".equalsIgnoreCase(type) ? COMPONENT_SERVER_EXTENSIONS : COMPONENT_JAVA_COORDINATES; 248 final String components = configuration.getProperty(key); 249 try { 250 final List<String> configLines = Files.readAllLines(config.toPath()); 251 if (components == null) { 252 final String original = configLines.stream().collect(joining("\n")); 253 try (final Writer writer = new FileWriter(config)) { 254 writer.write(original + "\n" + key + " = " + mainGav); 255 } 256 } else { 257 // backup 258 final File backup = new File(config.getParentFile(), "backup/" + config.getName() + "_" 259 + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-mm-dd_HH-mm-ss"))); 260 backup.getParentFile().mkdirs(); 261 try (final OutputStream to = new BufferedOutputStream(new FileOutputStream(backup))) { 262 Files.copy(config.toPath(), to); 263 } 264 265 boolean skip = false; 266 try (final Writer writer = new FileWriter(config)) { 267 for (final String line : configLines) { 268 if (line.trim().startsWith(key)) { 269 skip = true; 270 continue; 271 } else if (skip && line.trim().contains("=")) { 272 skip = false; 273 } else if (skip) { 274 continue; 275 } 276 writer.write(line + "\n"); 277 } 278 final String toFilter = Stream 279 .of(mainGav.contains(":") ? mainGav.split(":") : mainGav.split("/")) 280 .limit(2) 281 .collect(Collectors.joining(":", "", ":")); 282 writer 283 .write(key + " = " + Stream 284 .concat(Stream.of(mainGav), 285 Stream 286 .of(components.trim().split(",")) 287 .map(String::trim) 288 .filter(it -> !it.isEmpty()) 289 .filter(it -> !it.startsWith(toFilter))) 290 .collect(joining(","))); 291 } 292 System.out.println(type + " registered."); 293 } 294 } catch (final IOException ioe) { 295 throw new IllegalStateException(ioe); 296 } 297 System.out.println(type + " deployed successfully."); 298 } 299 300 private static Properties installJars(final File m2Root, final boolean forceOverwrite) { 301 String mainGav = null; 302 final Properties properties = new Properties(); 303 try (final JarInputStream jar = 304 new JarInputStream(new BufferedInputStream(new FileInputStream(jarLocation(CarMain.class))))) { 305 JarEntry entry; 306 while ((entry = jar.getNextJarEntry()) != null) { 307 if (entry.isDirectory()) { 308 continue; 309 } 310 if (entry.getName().startsWith("MAVEN-INF/repository/")) { 311 final String path = entry.getName().substring("MAVEN-INF/repository/".length()); 312 final File output = new File(m2Root, path); 313 if (!output.getCanonicalPath().startsWith(m2Root.getCanonicalPath())) { 314 throw new IOException("The output file is not contained in the destination directory"); 315 } 316 if (!output.exists() || forceOverwrite) { 317 output.getParentFile().mkdirs(); 318 Files.copy(jar, output.toPath(), StandardCopyOption.REPLACE_EXISTING); 319 } 320 } else if ("TALEND-INF/metadata.properties".equals(entry.getName())) { 321 // mainGav 322 properties.load(jar); 323 mainGav = properties.getProperty(COMPONENT_COORDINATES); 324 } 325 } 326 } catch (final IOException e) { 327 throw new IllegalArgumentException(e); 328 } 329 if (mainGav == null || mainGav.trim().isEmpty()) { 330 throw new IllegalArgumentException("Didn't find the component coordinates"); 331 } 332 System.out.println(String.format("Connector %s and dependencies jars installed to %s.", mainGav, m2Root)); 333 return properties; 334 } 335 336 private static Properties readProperties(final File config) { 337 final Properties configuration = new Properties(); 338 try (final InputStream stream = new FileInputStream(config)) { 339 configuration.load(stream); 340 } catch (final IOException e) { 341 throw new IllegalArgumentException(e); 342 } 343 return configuration; 344 } 345 346 private static void help() { 347 System.err.println("Usage:\n\njava -jar " + jarLocation(CarMain.class).getName() + " [command] [arguments]"); 348 System.err.println("commands:"); 349 System.err.println(" studio-deploy"); 350 System.err.println(" arguments:"); 351 System.err.println(" --location <location>: path to studio"); 352 System.err.println(" or"); 353 System.err.println(" <location>: path to studio"); 354 System.err.println(" -f : force overwrite existing jars"); 355 System.err.println(); 356 System.err.println(" maven-deploy"); 357 System.err.println(" arguments:"); 358 System.err.println(" --location <location>: path to .m2 repository"); 359 System.err.println(" or"); 360 System.err.println(" <location>: path to .m2 repository"); 361 System.err.println(" -f : force overwrite existing jars"); 362 System.err.println(); 363 System.err.println(" deploy-to-nexus"); 364 System.err.println(" arguments:"); 365 System.err.println(" --url <nexusUrl>: nexus server url"); 366 System.err.println(" --repo <repositoryName>: nexus repository name to upload dependencies to"); 367 System.err.println(" --user <username>: username to connect to nexus(optional)"); 368 System.err.println(" --pass <password>: password to connect to nexus(optional)"); 369 System.err 370 .println( 371 " --threads <parallelThreads>: threads number to use during upload to nexus(optional)"); 372 System.err.println(" --dir <tempDirectory>: temporary directory to use during upload(optional)"); 373 } 374 375 private static File jarLocation(final Class clazz) { 376 try { 377 final String classFileName = clazz.getName().replace(".", "/") + ".class"; 378 final ClassLoader loader = clazz.getClassLoader(); 379 return jarFromResource(loader, classFileName); 380 } catch (final RuntimeException e) { 381 throw e; 382 } catch (final Exception e) { 383 throw new IllegalStateException(e); 384 } 385 } 386 387 private static File jarFromResource(final ClassLoader loader, final String resourceName) { 388 try { 389 final URL url = loader.getResource(resourceName); 390 if (url == null) { 391 throw new IllegalStateException("didn't find " + resourceName); 392 } 393 if ("jar".equals(url.getProtocol())) { 394 final String spec = url.getFile(); 395 final int separator = spec.indexOf('!'); 396 return new File( 397 URLDecoder.decode(new URL(spec.substring(0, separator)).getFile(), UTF_8_ENC)); 398 } else if ("file".equals(url.getProtocol())) { 399 return toFile(resourceName, url); 400 } else { 401 throw new IllegalArgumentException("Unsupported URL scheme: " + url.toExternalForm()); 402 } 403 } catch (final RuntimeException e) { 404 throw e; 405 } catch (final Exception e) { 406 throw new IllegalStateException(e); 407 } 408 } 409 410 private static File toFile(final String classFileName, final URL url) throws UnsupportedEncodingException { 411 final String path = url.getFile(); 412 return new File( 413 URLDecoder.decode(path.substring(0, path.length() - classFileName.length()), UTF_8_ENC)); 414 } 415 416 private static void deployToNexus(final String serverUrl, final String repositoryName, final String username, 417 final String password, final int parallelThreads, final String tempDirLocation) { 418 String mainGav = null; 419 final List<JarEntry> entriesToProcess = new ArrayList<>(); 420 try (JarFile jar = new JarFile(jarLocation(CarMain.class))) { 421 Enumeration<JarEntry> entries = jar.entries(); 422 JarEntry entry; 423 while (entries.hasMoreElements()) { 424 entry = entries.nextElement(); 425 if (entry.isDirectory()) { 426 continue; 427 } 428 if (entry.getName().startsWith("MAVEN-INF/repository/")) { 429 entriesToProcess.add(entry); 430 } else if ("TALEND-INF/metadata.properties".equals(entry.getName())) { 431 // mainGav 432 final Properties properties = new Properties(); 433 properties.load(jar.getInputStream(entry)); 434 mainGav = properties.getProperty(COMPONENT_COORDINATES); 435 } 436 } 437 if (mainGav == null || mainGav.trim().isEmpty()) { 438 throw new IllegalArgumentException("Didn't find the component coordinates"); 439 } 440 uploadEntries(serverUrl, repositoryName, username, password, entriesToProcess, jar, parallelThreads, 441 tempDirLocation); 442 } catch (final IOException e) { 443 throw new IllegalArgumentException(e); 444 } 445 System.out 446 .println("Installed " + jarLocation(CarMain.class).getName() + " on " + serverUrl + ", " 447 + "you can now register '" + mainGav + "' component in your application."); 448 } 449 450 private static void uploadEntries(final String serverUrl, final String repositoryName, final String username, 451 final String password, final List<JarEntry> entriesToProcess, final JarFile jar, final int parallelThreads, 452 final String tempDirLocation) { 453 if (entriesToProcess.isEmpty()) { 454 return; 455 } 456 final File tempDirectory; 457 if (tempDirLocation == null || tempDirLocation.isEmpty()) { 458 System.out.println("No temporary directory is set. Creating a new one..."); 459 try { 460 tempDirectory = Files.createTempDirectory("car-deploy-to-nexus").toFile(); 461 } catch (final IOException e1) { 462 String message = "Could not create temp directory: " + e1.getMessage(); 463 throw new UnsupportedOperationException(message, e1); 464 } 465 } else { 466 tempDirectory = new File(tempDirLocation, "car-deploy-to-nexus-" + UUID.randomUUID().toString()); 467 tempDirectory.mkdirs(); 468 if (!tempDirectory.exists() || !(tempDirectory.canWrite() && tempDirectory.canRead())) { 469 throw new IllegalArgumentException("Cannot access temporary directory " + tempDirLocation); 470 } 471 } 472 System.out.println(tempDirectory.getPath() + " will be used as temporary directory."); 473 final String basicAuth = getAuthHeader(username, password); 474 final String nexusVersion = getNexusVersion(serverUrl, username, password, basicAuth); 475 final ExecutorService executor = 476 Executors.newFixedThreadPool(Math.min(entriesToProcess.size(), parallelThreads)); 477 try { 478 final CountDownLatch latch = new CountDownLatch(entriesToProcess.size()); 479 for (final JarEntry entry : entriesToProcess) { 480 final String path = entry.getName().substring("MAVEN-INF/repository/".length()); 481 executor.execute(() -> { 482 try { 483 if (!artifactExists(nexusVersion, serverUrl, basicAuth, repositoryName, path)) { 484 final File extracted = extractJar(tempDirectory, jar, entry); 485 sendJar(nexusVersion, serverUrl, basicAuth, repositoryName, path, extracted); 486 sendPom(nexusVersion, serverUrl, basicAuth, repositoryName, path, extracted); 487 } 488 } catch (final Exception e) { 489 System.err.println("A problem occured while uploading artifact: " + e.getMessage()); 490 } finally { 491 latch.countDown(); 492 } 493 }); 494 } 495 try { 496 latch.await(); 497 } catch (InterruptedException e) { 498 System.err.println("Exception caught while awaiting for latch: " + e.getMessage()); 499 } 500 } finally { 501 executor.shutdown(); 502 try { 503 executor.awaitTermination(1, TimeUnit.DAYS); 504 } catch (final InterruptedException e) { 505 System.err.println("Interrupted while awaiting for executor to shutdown."); 506 Thread.currentThread().interrupt(); 507 } 508 try { 509 System.out.println("Removing " + tempDirectory.getPath()); 510 if (tempDirectory.exists()) { 511 removeTempDirectoryRecursively(tempDirectory); 512 } 513 } catch (Exception e) { 514 System.err.println("Couldn't remove " + tempDirectory.getPath() + ": " + e.getMessage()); 515 } 516 } 517 } 518 519 private static void removeTempDirectoryRecursively(final File file) { 520 if (file.exists() && file.isFile()) { 521 file.delete(); 522 } else if (file.isDirectory()) { 523 final File[] files = file.listFiles(); 524 if (files != null) { 525 for (final File child : files) { 526 removeTempDirectoryRecursively(child); 527 } 528 } 529 file.delete(); 530 } 531 } 532 533 private static File extractJar(final File destDirectory, final JarFile jar, final JarEntry entry) 534 throws IOException { 535 File extracted; 536 try (final InputStream is = jar.getInputStream(entry)) { 537 final String fileName = entry.getName().substring(entry.getName().lastIndexOf("/") + 1); 538 extracted = File.createTempFile("temp-", fileName, destDirectory); 539 Files.copy(is, extracted.toPath(), StandardCopyOption.REPLACE_EXISTING); 540 } 541 return extracted; 542 } 543 544 private static String getAuthHeader(final String username, final String password) { 545 if (username == null || username.isEmpty()) { 546 return null; 547 } 548 return "Basic " + Base64 549 .getEncoder() 550 .encodeToString((username + (password == null || password.isEmpty() ? "" : ":" + password)).getBytes()); 551 } 552 553 private static boolean artifactExists(final String nexusVersion, final String serverUrl, final String basicAuth, 554 final String repositoryName, final String path) throws IOException { 555 HttpURLConnection conn = null; 556 try { 557 final URL url = new URL(getNexusUploadUrl(nexusVersion, serverUrl, repositoryName, path)); 558 conn = HttpURLConnection.class.cast(url.openConnection()); 559 conn.setDoOutput(true); 560 conn.setRequestMethod("GET"); 561 if (basicAuth != null) { 562 conn.setRequestProperty("Authorization", basicAuth); 563 } 564 conn.connect(); 565 if (conn.getResponseCode() == 404) { 566 return false; 567 } else if (conn.getResponseCode() == 401) { 568 throw new IllegalArgumentException("Authentication failed!"); 569 } else if (conn.getResponseCode() == 400) { 570 System.out.println("Ignoring " + path + ", it is likely not deployed on the right repository."); 571 } else { 572 System.out.println("Artifact " + path + " already exists on " + serverUrl + ". Skipping."); 573 } 574 } finally { 575 if (conn != null) { 576 conn.disconnect(); 577 } 578 } 579 return true; 580 } 581 582 private static void sendPom(final String nexusVersion, final String serverUrl, final String basicAuth, 583 final String repositoryName, final String path, final File jarFile) throws IOException { 584 final String pomPath = getPomPathFromPath(path); 585 System.out.println("Path of pom file resolved: " + pomPath); 586 try (final JarFile jar = new JarFile(jarFile)) { 587 JarEntry entry = jar.getJarEntry(pomPath); 588 if (entry == null) { 589 throw new FileNotFoundException("Could not find " + pomPath + " inside " + jar.getName()); 590 } 591 try (final InputStream jarIs = jar.getInputStream(entry)) { 592 final String serverPomPath = path.substring(0, path.lastIndexOf(".")) + ".pom"; 593 sendData(nexusVersion, serverUrl, basicAuth, repositoryName, serverPomPath, jarIs); 594 } 595 } 596 } 597 598 private static String getPomPathFromPath(final String path) { 599 final String parentPath = path.substring(0, path.lastIndexOf("/")); 600 final String version = parentPath.substring(parentPath.lastIndexOf("/") + 1); 601 final String fileName = path.substring(path.lastIndexOf("/") + 1); 602 final int versionIndex = fileName.indexOf(version); 603 final String artifactName; 604 if (versionIndex > 0) { 605 artifactName = fileName.substring(0, versionIndex - 1); 606 } else if (fileName.endsWith(".jar")) { 607 artifactName = fileName.substring(0, fileName.length() - 4); 608 } else { 609 artifactName = fileName; 610 } 611 String group = parentPath.substring(0, parentPath.lastIndexOf(artifactName)); 612 if (group.startsWith("/")) { 613 group = group.substring(1); 614 } 615 if (group.endsWith("/")) { 616 group = group.substring(0, group.length() - 1); 617 } 618 group = group.replace("/", "."); 619 return "META-INF/maven/" + group + "/" + artifactName + "/pom.xml"; 620 } 621 622 private static void sendJar(final String nexusVersion, final String serverUrl, final String basicAuth, 623 final String repositoryName, final String path, final File jarFile) throws IOException { 624 try (InputStream is = new FileInputStream(jarFile)) { 625 sendData(nexusVersion, serverUrl, basicAuth, repositoryName, path, is); 626 } 627 } 628 629 private static void sendData(final String nexusVersion, final String serverUrl, final String basicAuth, 630 final String repositoryName, final String path, final InputStream is) throws IOException { 631 System.out.println("Uploading " + path + " to " + serverUrl); 632 HttpURLConnection conn = null; 633 try { 634 URL url = new URL(getNexusUploadUrl(nexusVersion, serverUrl, repositoryName, path)); 635 conn = (HttpURLConnection) url.openConnection(); 636 conn.setDoOutput(true); 637 conn.setRequestMethod(getRequestMethod(nexusVersion)); 638 if (basicAuth != null) { 639 conn.setRequestProperty("Authorization", basicAuth); 640 } 641 conn.setRequestProperty("Content-Type", "multipart/form-data"); 642 conn.setRequestProperty("Accept", "*/*"); 643 conn.connect(); 644 try (OutputStream out = conn.getOutputStream()) { 645 byte[] buffer = new byte[1024]; 646 int bytesRead = -1; 647 while ((bytesRead = is.read(buffer)) != -1) { 648 out.write(buffer, 0, bytesRead); 649 } 650 out.flush(); 651 if (conn.getResponseCode() != 201) { 652 throw new IOException(conn.getResponseCode() + " - " + conn.getResponseMessage()); 653 } 654 } 655 System.out.println(path + " Uploaded"); 656 } finally { 657 if (conn != null) { 658 conn.disconnect(); 659 } 660 } 661 } 662 663 // note: maybe move to restapi. see 664 // https://support.sonatype.com/hc/en-us/articles/115006744008-How-can-I-programmatically-upload-files-into-Nexus-3- 665 private static String getNexusUploadUrl(final String nexusVersion, final String serverUrl, 666 final String repositoryName, final String path) { 667 if (nexusVersion.equals("V2")) { 668 return getUploadUrl(serverUrl, "content/repositories", repositoryName, path); 669 } else if (nexusVersion.startsWith("V3")) { 670 return getUploadUrl(serverUrl, "repository", repositoryName, path); 671 } 672 throw new IllegalArgumentException("Unknown Nexus version: " + nexusVersion); 673 } 674 675 private static String getUploadUrl(final String serverUrl, final String repositoriesLocation, final String repo, 676 final String path) { 677 return serverUrl + (serverUrl.endsWith("/") ? "" : "/") + repositoriesLocation + "/" + repo + "/" + path; 678 } 679 680 private static String getNexusVersion(final String serverUrl, final String username, final String password, 681 final String auth) { 682 System.out.println("Checking " + serverUrl + " API version."); 683 final String version; 684 if (isV2(serverUrl, username, password, auth)) { 685 version = "V2"; 686 } else if (isStableV3(serverUrl, username, password, auth)) { 687 version = "V3"; 688 } else if (isBetaV3(serverUrl, username, password, auth)) { 689 version = "V3Beta"; 690 } else { 691 throw new UnsupportedOperationException( 692 "Provided url doesn't respond neither to Nexus 2 nor to Nexus 3 endpoints."); 693 } 694 System.out.println("Nexus API version is recognized as " + version); 695 return version; 696 } 697 698 private static boolean isV2(final String serverUrl, final String username, final String password, 699 final String auth) { 700 System.out.println("Checking for V2 version..."); 701 HttpURLConnection conn = null; 702 try { 703 conn = prepareGet(serverUrl, username, password, "service/local/status", "*/*", auth); 704 if (conn.getResponseCode() >= 200 && conn.getResponseCode() <= 299) { 705 try (InputStream is = conn.getInputStream()) { 706 final byte[] b = new byte[1024]; 707 final StringBuilder out = new StringBuilder(); 708 int read; 709 while ((read = is.read(b, 0, b.length)) > 0) { 710 out.append(new String(b, 0, read)); 711 } 712 if (out.toString().contains("\"apiVersion\":\"2.")) { 713 System.out.println("version is v2"); 714 return true; 715 } 716 } 717 } 718 } catch (final IOException e) { 719 // no-op 720 } finally { 721 if (conn != null) { 722 conn.disconnect(); 723 } 724 } 725 return false; 726 } 727 728 private static boolean isBetaV3(final String serverUrl, final String username, final String password, 729 final String auth) { 730 System.out.println("Checking for V3Beta version..."); 731 return get(serverUrl, username, password, "service/rest/beta/repositories", auth); 732 } 733 734 private static boolean isStableV3(final String serverUrl, final String username, final String password, 735 final String auth) { 736 System.out.println("Checking for V3 version..."); 737 return get(serverUrl, username, password, "service/rest/v1/repositories", auth); 738 } 739 740 private static boolean get(final String serverUrl, final String username, final String password, final String path, 741 final String auth) { 742 boolean passed = false; 743 HttpURLConnection conn = null; 744 try { 745 conn = prepareGet(serverUrl, username, password, path, "application/json", auth); 746 if (conn.getResponseCode() == 200) { 747 passed = true; 748 } 749 } catch (final IOException e) { 750 // no-op 751 } finally { 752 if (conn != null) { 753 conn.disconnect(); 754 } 755 } 756 return passed; 757 } 758 759 private static HttpURLConnection prepareGet(final String serverUrl, final String username, final String password, 760 final String path, final String accept, final String auth) throws IOException { 761 final URL url = new URL(serverUrl + (serverUrl.endsWith("/") ? "" : "/") + path); 762 System.out.println("Sending GET request to " + url.getPath()); 763 final HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 764 conn.setDoInput(true); 765 final String userpass = username + ":" + password; 766 final String basicAuth = "Basic " + Base64.getEncoder().encodeToString(userpass.getBytes()); 767 conn.setRequestMethod("GET"); 768 if (auth != null) { 769 conn.setRequestProperty("Authorization", auth); 770 } 771 conn.setRequestProperty("Accept", accept); 772 conn.connect(); 773 return conn; 774 } 775 776 private static String getRequestMethod(final String nexusVersion) { 777 if ("V2".equals(nexusVersion)) { 778 return "POST"; 779 } else if (nexusVersion.startsWith("V3")) { 780 return "PUT"; 781 } 782 throw new IllegalArgumentException("Unknown Nexus version: " + nexusVersion); 783 } 784}