001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.ui.apps.git; 029 030import org.opencms.configuration.CmsConfigurationException; 031import org.opencms.file.CmsObject; 032import org.opencms.importexport.CmsImportExportException; 033import org.opencms.main.CmsException; 034import org.opencms.main.CmsLog; 035import org.opencms.main.OpenCms; 036import org.opencms.module.CmsModule; 037import org.opencms.module.CmsModuleImportExportHandler; 038import org.opencms.module.CmsModuleManager; 039import org.opencms.report.CmsPrintStreamReport; 040import org.opencms.report.I_CmsReport; 041import org.opencms.security.CmsRoleViolationException; 042import org.opencms.util.CmsFileUtil; 043import org.opencms.util.CmsFileUtil.FileWalkState; 044import org.opencms.util.CmsStringUtil; 045import org.opencms.xml.CmsXmlEntityResolver; 046import org.opencms.xml.CmsXmlUtils; 047 048import java.io.File; 049import java.io.FileInputStream; 050import java.io.FileNotFoundException; 051import java.io.FileOutputStream; 052import java.io.IOException; 053import java.io.OutputStream; 054import java.io.PrintStream; 055import java.lang.ProcessBuilder.Redirect; 056import java.nio.file.Paths; 057import java.util.Collection; 058import java.util.Date; 059import java.util.HashSet; 060import java.util.LinkedList; 061import java.util.List; 062import java.util.Set; 063import java.util.zip.ZipEntry; 064import java.util.zip.ZipOutputStream; 065 066import org.apache.commons.collections.Closure; 067import org.apache.commons.logging.Log; 068 069import org.dom4j.Document; 070import org.dom4j.Node; 071 072import com.google.common.collect.HashMultimap; 073import com.google.common.collect.Lists; 074import com.google.common.collect.Multimap; 075import com.google.common.collect.Sets; 076 077/** The class provides methods to automatically export modules from OpenCms and check in the exported, 078 * unzipped modules into some git repository. 079 * The feature is only available under Linux at the moment. It uses a shell script. 080 * Which modules are exported to and checked in to which git repository is configured in the file 081 * <code>/WEB-INF/git-scripts/module-checkin.sh</code>. 082 * */ 083public class CmsGitCheckin { 084 085 /** The log file for the git check in. */ 086 private static final String DEFAULT_LOGFILE_PATH = OpenCms.getSystemInfo().getWebInfRfsPath() + "logs/git.log"; 087 /** The variable under which the export path is set. */ 088 /** The default path to the script. */ 089 private static final String DEFAULT_RFS_PATH = OpenCms.getSystemInfo().getWebInfRfsPath() + "git-scripts/"; 090 /** The default folder for configuration files. */ 091 private static final String DEFAULT_CONFIG_FOLDER = DEFAULT_RFS_PATH + "config/"; 092 /** The default script file used for the git check in. */ 093 private static final String DEFAULT_SCRIPT_FILE = DEFAULT_RFS_PATH + "module-checkin.sh"; 094 /** The default configuration file used for the git check in. */ 095 private static final String DEFAULT_CONFIG_FILE = DEFAULT_RFS_PATH + "module-checkin.conf"; 096 /** Logger instance for this class. */ 097 private static final Log LOG = CmsLog.getLog(CmsGitCheckin.class); 098 /** Lock used to prevent simultaneous execution of checkIn method. */ 099 private static final Object STATIC_LOCK = new Object(); 100 101 /** Flag, indicating if an automatic pull should be performed after commit. */ 102 private Boolean m_autoPullAfter; 103 /** Flag, indicating if an automatic pull should be performed first. */ 104 private Boolean m_autoPullBefore; 105 /** Flag, indicating if an automatic push should be performed in the end. */ 106 private Boolean m_autoPush; 107 /** The checkout flag. */ 108 private boolean m_checkout; 109 /** The CMS context. */ 110 private CmsObject m_cms; 111 /** The commit message. */ 112 private String m_commitMessage; 113 /** Flag, indicating if modules should be exported and unzipped. */ 114 private Boolean m_copyAndUnzip; 115 116 /** The commit mode. */ 117 private Boolean m_commitMode; 118 119 /** Fetch and reset option. */ 120 private boolean m_fetchAndResetBeforeImport; 121 122 /** Stream for the log file. */ 123 private PrintStream m_logStream; 124 125 /** The modules that should really be exported and checked in. */ 126 private Collection<String> m_modulesToExport; 127 128 /** Flag, indicating if reset on HEAD should be performed. */ 129 private boolean m_resetHead; 130 131 /** Flag, indicating if reset on ${origin}/${branch} should be performed. */ 132 private boolean m_resetRemoteHead; 133 134 /** Flag, indicating if the lib/ folder of the modules should be deleted before the commit. */ 135 private Boolean m_excludeLibs; 136 /** The git user email. */ 137 private String m_gitUserEmail; 138 /** The git user name. */ 139 private String m_gitUserName; 140 /** Flag, indicating if execution of the script should go on for an unclean repository. */ 141 private Boolean m_ignoreUnclean; 142 143 /** The current configuration. */ 144 private CmsGitConfiguration m_currentConfiguration; 145 /** The available configurations. */ 146 private List<CmsGitConfiguration> m_configurations = new LinkedList<CmsGitConfiguration>(); 147 148 /** 149 * Default constructor. Initializing member variables with default values. 150 * 151 * @param cms the CMS context to use 152 */ 153 public CmsGitCheckin(CmsObject cms) { 154 155 m_cms = cms; 156 m_configurations = readConfigFiles(); 157 for (CmsGitConfiguration config : m_configurations) { 158 if (config.isValid()) { 159 m_currentConfiguration = config; 160 break; 161 } 162 } 163 } 164 165 /** 166 * Creates ZIP file data from the files / subfolders of the given root folder, and sends it to the given stream.<p> 167 * 168 * The stream passed as an argument is closed after the data is written. 169 * 170 * @param root the folder to zip 171 * @param zipOutput the output stream which the zip file data should be written to 172 * 173 * @throws Exception if something goes wrong 174 */ 175 public static void zipRfsFolder(final File root, final OutputStream zipOutput) throws Exception { 176 177 final ZipOutputStream zip = new ZipOutputStream(zipOutput); 178 try { 179 CmsFileUtil.walkFileSystem(root, new Closure() { 180 181 @SuppressWarnings("resource") 182 public void execute(Object stateObj) { 183 184 try { 185 FileWalkState state = (FileWalkState)stateObj; 186 for (File file : state.getFiles()) { 187 String relativePath = Paths.get(root.getAbsolutePath()).relativize( 188 Paths.get(file.getAbsolutePath())).toString(); 189 ZipEntry entry = new ZipEntry(relativePath); 190 entry.setTime(file.lastModified()); 191 zip.putNextEntry(entry); 192 zip.write(CmsFileUtil.readFully(new FileInputStream(file))); 193 zip.closeEntry(); 194 } 195 } catch (Exception e) { 196 throw new RuntimeException(e); 197 } 198 } 199 200 }); 201 } catch (RuntimeException e) { 202 if (e.getCause() instanceof Exception) { 203 throw (Exception)(e.getCause()); 204 } else { 205 throw e; 206 } 207 208 } 209 zip.flush(); 210 zip.close(); 211 212 } 213 214 /** Adds a module to the modules that should be exported. 215 * If called at least once, the explicitly added modules will be exported 216 * instead of the default modules. 217 * 218 * @param moduleName the name of the module to export. 219 */ 220 public void addModuleToExport(final String moduleName) { 221 222 if (m_modulesToExport == null) { 223 m_modulesToExport = new HashSet<String>(); 224 } 225 m_modulesToExport.add(moduleName); 226 } 227 228 /** 229 * Start export and check in of the selected modules. 230 * @return The exit code of the check in procedure (like a script's exit code). 231 */ 232 public int checkIn() { 233 234 try { 235 synchronized (STATIC_LOCK) { 236 m_logStream = new PrintStream(new FileOutputStream(DEFAULT_LOGFILE_PATH, false)); 237 CmsObject cms = getCmsObject(); 238 if (cms != null) { 239 return checkInInternal(); 240 } else { 241 m_logStream.println("No CmsObject given. Did you call init() first?"); 242 return -1; 243 } 244 } 245 } catch (FileNotFoundException e) { 246 e.printStackTrace(); 247 return -2; 248 } 249 } 250 251 /** 252 * Clears the selected modules.<p> 253 */ 254 public void clearModules() { 255 256 if (m_modulesToExport != null) { 257 m_modulesToExport.clear(); 258 } 259 } 260 261 /** 262 * Gets the checkout flag.<p> 263 * 264 * @return the checkout flag 265 */ 266 public boolean getCheckout() { 267 268 return m_checkout; 269 } 270 271 /** 272 * Gets the CMS context.<p> 273 * 274 * @return the CMS context 275 */ 276 public CmsObject getCmsObject() { 277 278 return m_cms; 279 } 280 281 /** 282 * Returns the commitMessage.<p> 283 * 284 * @return the commitMessage 285 */ 286 public String getCommitMessage() { 287 288 return m_commitMessage; 289 } 290 291 /** Returns the available configurations. 292 * @return the available configurations. 293 */ 294 public Collection<CmsGitConfiguration> getConfigurations() { 295 296 return m_configurations; 297 } 298 299 /** 300 * Returns the currently used configuration. 301 * @return the currently used configuration. 302 */ 303 public CmsGitConfiguration getCurrentConfiguration() { 304 305 return m_currentConfiguration; 306 } 307 308 /** 309 * Returns the gitUserEmail.<p> 310 * 311 * @return the gitUserEmail 312 */ 313 public String getGitUserEmail() { 314 315 return m_gitUserEmail; 316 } 317 318 /** 319 * Returns the gitUserName.<p> 320 * 321 * @return the gitUserName 322 */ 323 public String getGitUserName() { 324 325 return m_gitUserName; 326 } 327 328 /** Returns the collection of all installed modules. 329 * 330 * @return the collection of all installed modules. 331 */ 332 public Collection<String> getInstalledModules() { 333 334 return OpenCms.getModuleManager().getModuleNames(); 335 } 336 337 /** Returns the path to the log file. 338 * @return the path to the log file. 339 */ 340 public String getLogFilePath() { 341 342 return DEFAULT_LOGFILE_PATH; 343 } 344 345 /** 346 * Gets the log text.<p> 347 * 348 * @return the log text 349 */ 350 @SuppressWarnings("resource") 351 public String getLogText() { 352 353 try { 354 String logFilePath = getLogFilePath(); 355 byte[] logData = CmsFileUtil.readFully(new FileInputStream(logFilePath)); 356 return new String(logData, "UTF-8"); 357 } catch (IOException e) { 358 return "Error reading log file: " + getLogFilePath(); 359 } 360 } 361 362 /** 363 * Returns true if at least one valid configuration is present. 364 * @return true if at least one valid configuration is present. 365 */ 366 public boolean hasValidConfiguration() { 367 368 return !m_configurations.isEmpty(); 369 } 370 371 /** 372 * Returns true if the 'fetch and reset' flag is set.<p> 373 * 374 * @return true if 'fetch and reset' flag is set 375 */ 376 public boolean isFetchAndResetBeforeImport() { 377 378 return m_fetchAndResetBeforeImport; 379 } 380 381 /** Tests if a module is installed. 382 * @param moduleName name of the module to check. 383 * @return Flag, indicating if the module is installed. 384 */ 385 public boolean isModuleInstalled(final String moduleName) { 386 387 return (OpenCms.getModuleManager().getModule(moduleName) != null); 388 } 389 390 /** 391 * Sets the checkout flag.<p> 392 * 393 * @param checkout the checkout flag 394 */ 395 public void setCheckout(boolean checkout) { 396 397 m_checkout = checkout; 398 } 399 400 /** Setter for the commit mode. 401 * @param autoCommit the commit mode to set. 402 */ 403 public void setCommit(final boolean autoCommit) { 404 405 m_commitMode = Boolean.valueOf(autoCommit); 406 } 407 408 /** Setter for the commit message. 409 * @param message the commit message to set. 410 */ 411 public void setCommitMessage(final String message) { 412 413 m_commitMessage = message; 414 } 415 416 /** Setter for the copy and unzip mode. 417 * @param copyAndUnzip the copy and unzip mode to set. 418 */ 419 public void setCopyAndUnzip(final boolean copyAndUnzip) { 420 421 m_copyAndUnzip = Boolean.valueOf(copyAndUnzip); 422 } 423 424 /** 425 * Sets the current configuration if it is a valid configuration. Otherwise the configuration is not set. 426 * @param configuration the configuration to set. 427 * @return flag, indicating if the configuration is set. 428 */ 429 public boolean setCurrentConfiguration(CmsGitConfiguration configuration) { 430 431 if ((null != configuration) && configuration.isValid()) { 432 m_currentConfiguration = configuration; 433 return true; 434 } 435 return false; 436 } 437 438 /** Setter for the exclude libs flag. 439 * @param excludeLibs flag, indicating if the lib/ folder of the modules should be deleted before the commit. 440 */ 441 public void setExcludeLibs(final boolean excludeLibs) { 442 443 m_excludeLibs = Boolean.valueOf(excludeLibs); 444 } 445 446 /** 447 * Sets the 'fetch and reset' flag.<p> 448 * 449 * If this flag is set, the script will be used to fetch the remote branch and reset the current branch to the remote state before 450 * trying to import the modules.<p> 451 * 452 * @param fetchAndReset the 'fetch and reset' flag 453 */ 454 public void setFetchAndResetBeforeImport(boolean fetchAndReset) { 455 456 m_fetchAndResetBeforeImport = fetchAndReset; 457 } 458 459 /** Setter for the git user email. 460 * @param useremail the git user email to set. 461 */ 462 public void setGitUserEmail(final String useremail) { 463 464 m_gitUserEmail = useremail; 465 } 466 467 /** Setter for the git user name. 468 * @param username the git user name to set. 469 */ 470 public void setGitUserName(final String username) { 471 472 m_gitUserName = username; 473 } 474 475 /** Setter for the ignore-unclean flag. 476 * @param ignore flag, indicating if an unclean repository should be ignored. 477 */ 478 public void setIgnoreUnclean(final boolean ignore) { 479 480 m_ignoreUnclean = Boolean.valueOf(ignore); 481 } 482 483 /** Setter for the pull-after flag. 484 * @param autoPull flag, indicating if a pull should be performed directly after the commit. 485 */ 486 public void setPullAfter(final boolean autoPull) { 487 488 m_autoPullAfter = Boolean.valueOf(autoPull); 489 } 490 491 /** Setter for the pull-before flag. 492 * @param autoPull flag, indicating if a pull should be performed first. 493 */ 494 public void setPullBefore(final boolean autoPull) { 495 496 m_autoPullBefore = Boolean.valueOf(autoPull); 497 } 498 499 /** Setter for the auto-push flag. 500 * @param autoPush flag, indicating if a push should be performed in the end. 501 */ 502 public void setPush(final boolean autoPush) { 503 504 m_autoPush = Boolean.valueOf(autoPush); 505 } 506 507 /** Setter for the reset-head flag. 508 * @param reset flag, indicating if a reset to the HEAD should be performed or not. 509 */ 510 public void setResetHead(final boolean reset) { 511 512 m_resetHead = reset; 513 } 514 515 /** Setter for the reset-remote-head flag. 516 * @param reset flag, indicating if a reset to the ${origin}/${branch} should be performed or not. 517 */ 518 public void setResetRemoteHead(final boolean reset) { 519 520 m_resetRemoteHead = reset; 521 } 522 523 /** 524 * Adds the configuration from <code>configFile</code> to the {@link Collection} of {@link CmsGitConfiguration}, 525 * if a valid configuration is read. Otherwise it logs the failure. 526 * @param configurations Collection of configurations where the new configuration should be added. 527 * @param configFile file to read the new configuration from. 528 */ 529 private void addConfigurationIfValid(final Collection<CmsGitConfiguration> configurations, final File configFile) { 530 531 CmsGitConfiguration config = null; 532 try { 533 if (configFile.isFile()) { 534 config = new CmsGitConfiguration(configFile); 535 if (config.isValid()) { 536 configurations.add(config); 537 } else { 538 throw new Exception( 539 "Could not read git configuration file" + config.getConfigurationFile().getAbsolutePath()); 540 } 541 } 542 } catch (NullPointerException npe) { 543 LOG.error("Could not read git configuration.", npe); 544 } catch (Exception e) { 545 String file = (null != config) 546 && (null != config.getConfigurationFile()) 547 && (null != config.getConfigurationFile().getAbsolutePath()) 548 ? config.getConfigurationFile().getAbsolutePath() 549 : "<unknown>"; 550 LOG.warn("Trying to read invalid git configuration from " + file + ".", e); 551 } 552 } 553 554 /** 555 * Export modules and check them in. Assumes the log stream already open. 556 * @return exit code of the commit-script. 557 */ 558 private int checkInInternal() { 559 560 m_logStream.println("[" + new Date() + "] STARTING Git task"); 561 m_logStream.println("========================="); 562 m_logStream.println(); 563 564 if (m_checkout) { 565 m_logStream.println("Running checkout script"); 566 567 } else if (!(m_resetHead || m_resetRemoteHead)) { 568 m_logStream.println("Exporting relevant modules"); 569 m_logStream.println("--------------------------"); 570 m_logStream.println(); 571 572 exportModules(); 573 574 m_logStream.println(); 575 m_logStream.println("Calling script to check in the exports"); 576 m_logStream.println("--------------------------------------"); 577 m_logStream.println(); 578 579 } else { 580 581 m_logStream.println(); 582 m_logStream.println("Calling script to reset the repository"); 583 m_logStream.println("--------------------------------------"); 584 m_logStream.println(); 585 586 } 587 588 int exitCode = runCommitScript(); 589 if (exitCode != 0) { 590 m_logStream.println(); 591 m_logStream.println("ERROR: Something went wrong. The script got exitcode " + exitCode + "."); 592 m_logStream.println(); 593 } 594 if ((exitCode == 0) && m_checkout) { 595 boolean importOk = importModules(); 596 if (!importOk) { 597 return -1; 598 } 599 } 600 m_logStream.println("[" + new Date() + "] FINISHED Git task"); 601 m_logStream.println(); 602 m_logStream.close(); 603 604 return exitCode; 605 } 606 607 /** Returns the command to run by the shell to normally run the checkin script. 608 * @return the command to run by the shell to normally run the checkin script. 609 */ 610 private String checkinScriptCommand() { 611 612 String exportModules = ""; 613 if ((m_modulesToExport != null) && !m_modulesToExport.isEmpty()) { 614 StringBuffer exportModulesParam = new StringBuffer(); 615 for (String moduleName : m_modulesToExport) { 616 exportModulesParam.append(" ").append(moduleName); 617 } 618 exportModulesParam.replace(0, 1, " \""); 619 exportModulesParam.append("\" "); 620 exportModules = " --modules " + exportModulesParam.toString(); 621 622 } 623 String commitMessage = ""; 624 if (m_commitMessage != null) { 625 commitMessage = " -msg \"" + m_commitMessage.replace("\"", "\\\"") + "\""; 626 } 627 String gitUserName = ""; 628 if (m_gitUserName != null) { 629 if (m_gitUserName.trim().isEmpty()) { 630 gitUserName = " --ignore-default-git-user-name"; 631 } else { 632 gitUserName = " --git-user-name \"" + m_gitUserName + "\""; 633 } 634 } 635 String gitUserEmail = ""; 636 if (m_gitUserEmail != null) { 637 if (m_gitUserEmail.trim().isEmpty()) { 638 gitUserEmail = " --ignore-default-git-user-email"; 639 } else { 640 gitUserEmail = " --git-user-email \"" + m_gitUserEmail + "\""; 641 } 642 } 643 String autoPullBefore = ""; 644 if (m_autoPullBefore != null) { 645 autoPullBefore = m_autoPullBefore.booleanValue() ? " --pull-before " : " --no-pull-before"; 646 } 647 String autoPullAfter = ""; 648 if (m_autoPullAfter != null) { 649 autoPullAfter = m_autoPullAfter.booleanValue() ? " --pull-after " : " --no-pull-after"; 650 } 651 String autoPush = ""; 652 if (m_autoPush != null) { 653 autoPush = m_autoPush.booleanValue() ? " --push " : " --no-push"; 654 } 655 String exportFolder = " --export-folder \"" + m_currentConfiguration.getModuleExportPath() + "\""; 656 String exportMode = " --export-mode " + m_currentConfiguration.getExportMode(); 657 String excludeLibs = ""; 658 if (m_excludeLibs != null) { 659 excludeLibs = m_excludeLibs.booleanValue() ? " --exclude-libs" : " --no-exclude-libs"; 660 } 661 String commitMode = ""; 662 if (m_commitMode != null) { 663 commitMode = m_commitMode.booleanValue() ? " --commit" : " --no-commit"; 664 } 665 String ignoreUncleanMode = ""; 666 if (m_ignoreUnclean != null) { 667 ignoreUncleanMode = m_ignoreUnclean.booleanValue() ? " --ignore-unclean" : " --no-ignore-unclean"; 668 } 669 String copyAndUnzip = ""; 670 if (m_copyAndUnzip != null) { 671 copyAndUnzip = m_copyAndUnzip.booleanValue() ? " --copy-and-unzip" : " --no-copy-and-unzip"; 672 } 673 674 String configFilePath = m_currentConfiguration.getFilePath(); 675 676 return "\"" 677 + DEFAULT_SCRIPT_FILE 678 + "\"" 679 + exportModules 680 + commitMessage 681 + gitUserName 682 + gitUserEmail 683 + autoPullBefore 684 + autoPullAfter 685 + autoPush 686 + exportFolder 687 + exportMode 688 + excludeLibs 689 + commitMode 690 + ignoreUncleanMode 691 + copyAndUnzip 692 + " \"" 693 + configFilePath 694 + "\""; 695 } 696 697 /** Returns the command to run by the shell to normally run the checkin script. 698 * @return the command to run by the shell to normally run the checkin script. 699 */ 700 private String checkoutScriptCommand() { 701 702 String configFilePath = m_currentConfiguration.getFilePath(); 703 return "\"" + DEFAULT_SCRIPT_FILE + "\"" + " --checkout " + " \"" + configFilePath + "\""; 704 } 705 706 /** 707 * Export the modules that should be checked in into git. 708 */ 709 private void exportModules() { 710 711 // avoid to export modules if unnecessary 712 if (((null != m_copyAndUnzip) && !m_copyAndUnzip.booleanValue()) 713 || ((null == m_copyAndUnzip) && !m_currentConfiguration.getDefaultCopyAndUnzip())) { 714 m_logStream.println(); 715 m_logStream.println("NOT EXPORTING MODULES - you disabled copy and unzip."); 716 m_logStream.println(); 717 return; 718 } 719 CmsModuleManager moduleManager = OpenCms.getModuleManager(); 720 721 Collection<String> modulesToExport = ((m_modulesToExport == null) || m_modulesToExport.isEmpty()) 722 ? m_currentConfiguration.getConfiguredModules() 723 : m_modulesToExport; 724 725 for (String moduleName : modulesToExport) { 726 CmsModule module = moduleManager.getModule(moduleName); 727 if (module != null) { 728 CmsModuleImportExportHandler handler = CmsModuleImportExportHandler.getExportHandler( 729 getCmsObject(), 730 module, 731 "Git export handler"); 732 try { 733 handler.exportData( 734 getCmsObject(), 735 new CmsPrintStreamReport( 736 m_logStream, 737 OpenCms.getWorkplaceManager().getWorkplaceLocale(getCmsObject()), 738 false)); 739 } catch (CmsRoleViolationException | CmsConfigurationException | CmsImportExportException e) { 740 e.printStackTrace(m_logStream); 741 } 742 } 743 } 744 } 745 746 /** 747 * Imports a module from the given zip file.<p> 748 * 749 * @param file the module file to import 750 * @throws CmsException if soemthing goes wrong 751 * 752 * @return true if there were no errors during the import 753 */ 754 private boolean importModule(File file) throws CmsException { 755 756 m_logStream.println("Trying to import module from " + file.getAbsolutePath()); 757 I_CmsReport report = new CmsPrintStreamReport( 758 m_logStream, 759 OpenCms.getWorkplaceManager().getWorkplaceLocale(getCmsObject()), 760 false); 761 OpenCms.getModuleManager().replaceModule(m_cms, file.getAbsolutePath(), report); 762 file.delete(); 763 if (report.hasError() || report.hasWarning()) { 764 m_logStream.println("Import failed, see opencms.log for details"); 765 return false; 766 } 767 return true; 768 } 769 770 /** 771 * Imports the selected modules from the git repository.<p> 772 * 773 * @return true if there were no errors during the import 774 */ 775 @SuppressWarnings("resource") 776 private boolean importModules() { 777 778 boolean result = true; 779 780 try { 781 m_logStream.println("Checking module dependencies."); 782 Multimap<String, String> dependencies = HashMultimap.create(); 783 Set<String> unsortedModules = Sets.newHashSet(m_modulesToExport); 784 for (String module : m_modulesToExport) { 785 String manifestPath = CmsStringUtil.joinPaths( 786 m_currentConfiguration.getModulesPath(), 787 module, 788 m_currentConfiguration.getResourcesSubFolder(), 789 "manifest.xml"); 790 Document doc = CmsXmlUtils.unmarshalHelper( 791 CmsFileUtil.readFully(new FileInputStream(manifestPath)), 792 new CmsXmlEntityResolver(null)); 793 794 List<?> depNodes = doc.getRootElement().selectNodes("//dependencies/dependency/@name"); 795 for (Object nodeObj : depNodes) { 796 Node node = ((Node)nodeObj); 797 String dependency = node.getText(); 798 if (m_modulesToExport.contains(dependency)) { 799 // we can only handle dependencies between selected modules 800 // and just have to assume that other dependencies are fulfilled 801 dependencies.put(module, dependency); 802 } 803 } 804 } 805 List<String> sortedModules = Lists.newArrayList(); 806 // if there are no cycles, this loop will find one element on each iteration 807 for (int i = 0; i < m_modulesToExport.size(); i++) { 808 String nextModule = null; 809 for (String key : unsortedModules) { 810 if (dependencies.get(key).isEmpty()) { 811 nextModule = key; 812 break; 813 } 814 } 815 if (nextModule != null) { 816 sortedModules.add(nextModule); 817 unsortedModules.remove(nextModule); 818 for (String key : Sets.newHashSet(dependencies.keySet())) { // copy key set to avoid concurrent modification exception 819 dependencies.get(key).remove(nextModule); 820 } 821 } 822 } 823 m_logStream.println("Modules sorted by dependencies: " + sortedModules); 824 for (String moduleName : sortedModules) { 825 String dir = CmsStringUtil.joinPaths( 826 m_currentConfiguration.getModulesPath(), 827 moduleName, 828 m_currentConfiguration.getResourcesSubFolder()); 829 File dirEntry = new File(dir); 830 if (!dirEntry.exists()) { 831 continue; 832 } 833 try { 834 m_logStream.println("Creating temp file for module " + moduleName); 835 File outputFile = File.createTempFile(moduleName + "-", ".zip"); 836 FileOutputStream fos = new FileOutputStream(outputFile); 837 m_logStream.println("Zipping module structure to " + outputFile.getAbsolutePath()); 838 zipRfsFolder(dirEntry, fos); 839 result &= importModule(outputFile); 840 outputFile.delete(); 841 } catch (Exception e) { 842 LOG.error(e.getLocalizedMessage(), e); 843 e.printStackTrace(m_logStream); 844 result = false; 845 } 846 } 847 } catch (Exception e) { 848 849 LOG.error(e.getLocalizedMessage(), e); 850 m_logStream.println("Unable to check dependencies for modules, giving up."); 851 e.printStackTrace(m_logStream); 852 result = false; 853 } 854 return result; 855 } 856 857 /** 858 * Read all configuration files. 859 * @return the list with all available configurations 860 */ 861 private List<CmsGitConfiguration> readConfigFiles() { 862 863 List<CmsGitConfiguration> configurations = new LinkedList<CmsGitConfiguration>(); 864 865 // Default configuration file for backwards compatibility 866 addConfigurationIfValid(configurations, new File(DEFAULT_CONFIG_FILE)); 867 868 // All files in the config folder 869 File configFolder = new File(DEFAULT_CONFIG_FOLDER); 870 if (configFolder.isDirectory()) { 871 for (File configFile : configFolder.listFiles()) { 872 addConfigurationIfValid(configurations, configFile); 873 } 874 } 875 return configurations; 876 } 877 878 /** Returns the command to run by the shell to reset to HEAD. 879 * @return the command to run by the shell to reset to HEAD. 880 */ 881 private String resetHeadScriptCommand() { 882 883 String configFilePath = m_currentConfiguration.getFilePath(); 884 885 return "\"" + DEFAULT_SCRIPT_FILE + "\" --reset-head" + " \"" + configFilePath + "\""; 886 } 887 888 /** Returns the command to run by the shell to reset to ${origin}/${branch}. 889 * @return the command to run by the shell to reset to ${origin}/${branch}. 890 */ 891 private String resetRemoteHeadScriptCommand() { 892 893 String configFilePath = m_currentConfiguration.getFilePath(); 894 895 return "\"" + DEFAULT_SCRIPT_FILE + "\" --reset-remote-head" + " \"" + configFilePath + "\""; 896 } 897 898 /** 899 * Runs the shell script for committing and optionally pushing the changes in the module. 900 * @return exit code of the script. 901 */ 902 private int runCommitScript() { 903 904 if (m_checkout && !m_fetchAndResetBeforeImport) { 905 m_logStream.println("Skipping script...."); 906 return 0; 907 } 908 try { 909 m_logStream.flush(); 910 String commandParam; 911 if (m_resetRemoteHead) { 912 commandParam = resetRemoteHeadScriptCommand(); 913 } else if (m_resetHead) { 914 commandParam = resetHeadScriptCommand(); 915 } else if (m_checkout) { 916 commandParam = checkoutScriptCommand(); 917 } else { 918 commandParam = checkinScriptCommand(); 919 } 920 String[] cmd = {"bash", "-c", commandParam}; 921 m_logStream.println("Calling the script as follows:"); 922 m_logStream.println(); 923 m_logStream.println(cmd[0] + " " + cmd[1] + " " + cmd[2]); 924 ProcessBuilder builder = new ProcessBuilder(cmd); 925 m_logStream.close(); 926 m_logStream = null; 927 Redirect redirect = Redirect.appendTo(new File(DEFAULT_LOGFILE_PATH)); 928 builder.redirectOutput(redirect); 929 builder.redirectError(redirect); 930 Process scriptProcess = builder.start(); 931 int exitCode = scriptProcess.waitFor(); 932 scriptProcess.getOutputStream().close(); 933 m_logStream = new PrintStream(new FileOutputStream(DEFAULT_LOGFILE_PATH, true)); 934 return exitCode; 935 } catch (InterruptedException | IOException e) { 936 e.printStackTrace(m_logStream); 937 return -1; 938 } 939 940 } 941}