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.module; 029 030import org.opencms.file.CmsFile; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsProject; 033import org.opencms.file.CmsProperty; 034import org.opencms.file.CmsResource; 035import org.opencms.file.CmsResourceFilter; 036import org.opencms.file.CmsVfsResourceNotFoundException; 037import org.opencms.file.types.I_CmsResourceType; 038import org.opencms.importexport.CmsImportParameters; 039import org.opencms.importexport.CmsImportResourceDataReader; 040import org.opencms.importexport.CmsImportVersion10; 041import org.opencms.importexport.CmsImportVersion10.RelationData; 042import org.opencms.importexport.Messages; 043import org.opencms.lock.CmsLock; 044import org.opencms.main.CmsException; 045import org.opencms.main.CmsLog; 046import org.opencms.main.CmsShell; 047import org.opencms.main.OpenCms; 048import org.opencms.relations.CmsRelation; 049import org.opencms.relations.CmsRelationFilter; 050import org.opencms.relations.CmsRelationType; 051import org.opencms.relations.I_CmsLinkParseable; 052import org.opencms.report.I_CmsReport; 053import org.opencms.security.CmsAccessControlEntry; 054import org.opencms.util.CmsFileUtil; 055import org.opencms.util.CmsStringUtil; 056import org.opencms.util.CmsUUID; 057 058import java.io.ByteArrayOutputStream; 059import java.io.PrintStream; 060import java.util.ArrayList; 061import java.util.Arrays; 062import java.util.Collection; 063import java.util.Collections; 064import java.util.HashMap; 065import java.util.HashSet; 066import java.util.List; 067import java.util.Map; 068import java.util.Optional; 069import java.util.Set; 070import java.util.stream.Collectors; 071 072import org.apache.commons.logging.Log; 073 074import com.google.common.base.Objects; 075import com.google.common.collect.Sets; 076 077/** 078 * Class used for updating modules.<p> 079 * 080 * This class updates modules in a smarter way than simply deleting and importing them again: The resources in the import 081 * ZIP file are compared to the resources in the currently installed module and only makes changes when necessary. The reason 082 * for this is that deletions of resources can be slow in some very large OpenCms installations, and the classic way of updating modules 083 * (delete/import) can take a long time because of this. 084 */ 085public class CmsModuleUpdater { 086 087 /** The logger instance for this class. */ 088 private static final Log LOG = CmsLog.getLog(CmsModuleUpdater.class); 089 090 /** Structure ids of imported resources.*/ 091 private Set<CmsUUID> m_importIds = new HashSet<CmsUUID>(); 092 093 /** The module data read from the ZIP. */ 094 private CmsModuleImportData m_moduleData; 095 096 /** The report to write to. */ 097 private I_CmsReport m_report; 098 099 /** 100 * Creates a new instance.<p> 101 * 102 * @param moduleData the module import data 103 * @param report the report to write to 104 */ 105 public CmsModuleUpdater(CmsModuleImportData moduleData, I_CmsReport report) { 106 107 m_moduleData = moduleData; 108 m_report = report; 109 } 110 111 /** 112 * Checks whether the module resources and sites of the two module versions are suitable for updating.<p> 113 * 114 * @param installedModule the installed module 115 * @param newModule the module to import 116 * 117 * @return true if the module resources are compatible 118 */ 119 public static boolean checkCompatibleModuleResources(CmsModule installedModule, CmsModule newModule) { 120 121 if (!(installedModule.hasOnlySystemAndSharedResources() && newModule.hasOnlySystemAndSharedResources())) { 122 String oldSite = installedModule.getSite(); 123 String newSite = newModule.getSite(); 124 if (!((oldSite != null) && (newSite != null) && CmsStringUtil.comparePaths(oldSite, newSite))) { 125 return false; 126 } 127 128 } 129 for (String oldModRes : installedModule.getResources()) { 130 for (String newModRes : newModule.getResources()) { 131 if (CmsStringUtil.isProperPrefixPath(oldModRes, newModRes)) { 132 return false; 133 } 134 } 135 } 136 return true; 137 138 } 139 140 /** 141 * Tries to create a new updater instance.<p> 142 * 143 * If the module is deemed non-updatable, an empty result is returned.<p> 144 * 145 * @param cms the current CMS context 146 * @param importFile the import file path 147 * @param report the report to write to 148 * @return an optional module updater 149 * 150 * @throws CmsException if something goes wrong 151 */ 152 public static Optional<CmsModuleUpdater> create(CmsObject cms, String importFile, I_CmsReport report) 153 throws CmsException { 154 155 CmsModuleImportData moduleData = readModuleData(cms, importFile, report); 156 if (moduleData.checkUpdatable(cms)) { 157 return Optional.of(new CmsModuleUpdater(moduleData, report)); 158 } else { 159 return Optional.empty(); 160 } 161 } 162 163 /** 164 * Check if a resource needs to be updated because of its direct fields.<p> 165 * 166 * @param existingRes the existing resource 167 * @param newRes the new resource 168 * @param reduced true if we are in reduced export mode 169 * 170 * @return true if we need to update the resource based on its direct fields 171 */ 172 public static boolean needToUpdateResourceFields(CmsResource existingRes, CmsResource newRes, boolean reduced) { 173 174 boolean result = false; 175 result |= existingRes.getTypeId() != newRes.getTypeId(); 176 result |= differentDates(existingRes.getDateCreated(), newRes.getDateCreated()); // Export format date is not precise to millisecond 177 result |= differentDates(existingRes.getDateReleased(), newRes.getDateReleased()); 178 result |= differentDates(existingRes.getDateExpired(), newRes.getDateExpired()); 179 result |= existingRes.getFlags() != newRes.getFlags(); 180 if (!reduced) { 181 result |= !Objects.equal(existingRes.getUserCreated(), newRes.getUserCreated()); 182 result |= !Objects.equal(existingRes.getUserLastModified(), newRes.getUserLastModified()); 183 result |= existingRes.getDateLastModified() != newRes.getDateLastModified(); 184 } 185 return result; 186 } 187 188 /** 189 * Normalizes the path.<p> 190 * 191 * @param pathComponents the path components 192 * 193 * @return the normalized path 194 */ 195 public static String normalizePath(String... pathComponents) { 196 197 return CmsFileUtil.removeTrailingSeparator(CmsStringUtil.joinPaths(pathComponents)); 198 } 199 200 /** 201 * Reads the module data from an import zip file.<p> 202 * 203 * @param cms the CMS context 204 * @param importFile the import file 205 * @param report the report to write to 206 * @return the module data 207 * @throws CmsException if something goes wrong 208 */ 209 public static CmsModuleImportData readModuleData(CmsObject cms, String importFile, I_CmsReport report) 210 throws CmsException { 211 212 CmsModuleImportData result = new CmsModuleImportData(); 213 CmsModule module = CmsModuleImportExportHandler.readModuleFromImport(importFile); 214 cms = OpenCms.initCmsObject(cms); 215 216 String importSite = module.getImportSite(); 217 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(importSite)) { 218 cms.getRequestContext().setSiteRoot(importSite); 219 } else { 220 String siteToSet = cms.getRequestContext().getSiteRoot(); 221 if ("".equals(siteToSet)) { 222 siteToSet = "/"; 223 } 224 module.setSite(siteToSet); 225 } 226 result.setModule(module); 227 result.setCms(cms); 228 CmsImportResourceDataReader importer = new CmsImportResourceDataReader(result); 229 CmsImportParameters params = new CmsImportParameters(importFile, "/", false); 230 importer.importData(cms, report, params); // This only reads the module data into Java objects 231 return result; 232 233 } 234 235 /** 236 * Checks that two longs representing dates differ by more than 1000 (milliseconds).<p> 237 * 238 * @param d1 the first date 239 * @param d2 the second date 240 * 241 * @return true if the dates differ by more than 1000 milliseconds 242 */ 243 static boolean differentDates(long d1, long d2) { 244 245 return 1000 < Math.abs(d2 - d1); 246 } 247 248 /** 249 * Gets all resources in the module.<p> 250 * 251 * @param cms the current CMS context 252 * @param module the module 253 * @return the resources in the module 254 * @throws CmsException if something goes wrong 255 */ 256 private static Set<CmsResource> getAllResourcesInModule(CmsObject cms, CmsModule module) throws CmsException { 257 258 Set<CmsResource> result = new HashSet<>(); 259 for (CmsResource resource : CmsModule.calculateModuleResources(cms, module)) { 260 result.add(resource); 261 if (resource.isFolder()) { 262 result.addAll(cms.readResources(resource, CmsResourceFilter.ALL, true)); 263 } 264 } 265 return result; 266 } 267 268 /** 269 * Update relations for all imported resources.<p> 270 * 271 * @param cms the current CMS context 272 * @throws CmsException if something goes wrong 273 */ 274 public void importRelations(CmsObject cms) throws CmsException { 275 276 for (CmsResourceImportData resData : m_moduleData.getResourceData()) { 277 if (!resData.getRelations().isEmpty()) { 278 CmsResource importResource = resData.getImportResource(); 279 if (importResource != null) { 280 importResource = cms.readResource(importResource.getStructureId(), CmsResourceFilter.ALL); 281 updateRelations(cms, importResource, resData.getRelations()); 282 } 283 } 284 } 285 286 } 287 288 /** 289 * Performs the module update.<p> 290 */ 291 public void run() { 292 293 try { 294 CmsObject cms = m_moduleData.getCms(); 295 CmsModule module = m_moduleData.getModule(); 296 CmsModule oldModule = OpenCms.getModuleManager().getModule(module.getName()); 297 Map<CmsUUID, CmsUUID> conflictingIds = m_moduleData.getConflictingIds(); 298 if (!conflictingIds.isEmpty()) { 299 deleteConflictingResources(cms, module, conflictingIds); 300 } 301 CmsProject importProject = createAndSetModuleImportProject(cms, module); 302 CmsModuleImportExportHandler.reportBeginImport(m_report, module.getName()); 303 304 Map<CmsUUID, CmsResourceImportData> importResourcesById = new HashMap<>(); 305 for (CmsResourceImportData resData : m_moduleData.getResourceData()) { 306 importResourcesById.put(resData.getResource().getStructureId(), resData); 307 } 308 Set<CmsResource> oldModuleResources = getAllResourcesInModule(cms, oldModule); 309 List<CmsResource> toDelete = new ArrayList<>(); 310 Set<String> immutables = OpenCms.getImportExportManager().getImmutableResources().stream().flatMap( 311 path -> Arrays.asList( 312 CmsFileUtil.removeTrailingSeparator(path), 313 CmsFileUtil.addTrailingSeparator(path)).stream()).collect(Collectors.toSet()); 314 for (CmsResource oldRes : oldModuleResources) { 315 if (immutables.contains(oldRes.getRootPath())) { 316 continue; 317 } 318 CmsResourceImportData newRes = importResourcesById.get(oldRes.getStructureId()); 319 if (newRes == null) { 320 toDelete.add(oldRes); 321 } 322 } 323 int index = 0; 324 for (CmsResourceImportData resData1 : m_moduleData.getResourceData()) { 325 index += 1; 326 processImportResource(cms, resData1, index); 327 } 328 processDeletions(cms, toDelete); 329 parseLinks(cms); 330 331 importRelations(cms); 332 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(module.getImportScript())) { 333 runImportScript(cms, module); 334 } 335 cms.unlockProject(importProject.getUuid()); 336 OpenCms.getModuleManager().updateModule(cms, module); 337 module.setCheckpointTime(System.currentTimeMillis()); 338 // reinitialize the resource manager with additional module resource types if necessary 339 if (module.getResourceTypes() != Collections.EMPTY_LIST) { 340 OpenCms.getResourceManager().initialize(cms); 341 } 342 // reinitialize the workplace manager with additional module explorer types if necessary 343 if (module.getExplorerTypes() != Collections.EMPTY_LIST) { 344 OpenCms.getWorkplaceManager().addExplorerTypeSettings(module); 345 } 346 OpenCms.getPublishManager().publishProject(cms, m_report); 347 OpenCms.getPublishManager().waitWhileRunning(); 348 CmsModuleImportExportHandler.reportEndImport(m_report); 349 } catch (Exception e) { 350 m_report.println(e); 351 } finally { 352 cleanUp(); 353 } 354 } 355 356 /** 357 * Updates the access control list fr a resource.<p> 358 * 359 * @param cms the current cms context 360 * @param resData the resource data 361 * @param resource the existing resource 362 * @return the resource 363 * 364 * @throws CmsException if something goes wrong 365 */ 366 public boolean updateAcls(CmsObject cms, CmsResourceImportData resData, CmsResource resource) throws CmsException { 367 368 boolean changed = false; 369 Map<CmsUUID, CmsAccessControlEntry> importAces = buildAceMap(resData.getAccessControlEntries()); 370 371 String path = cms.getSitePath(resource); 372 List<CmsAccessControlEntry> existingAcl = cms.getAccessControlEntries(path, false); 373 Map<CmsUUID, CmsAccessControlEntry> existingAces = buildAceMap(existingAcl); 374 Set<CmsUUID> keys = new HashSet<>(existingAces.keySet()); 375 keys.addAll(importAces.keySet()); 376 for (CmsUUID key : keys) { 377 CmsAccessControlEntry existingEntry = existingAces.get(key); 378 CmsAccessControlEntry newEntry = importAces.get(key); 379 if ((existingEntry == null) 380 || (newEntry == null) 381 || !existingEntry.withNulledResource().equals(newEntry.withNulledResource())) { 382 cms.importAccessControlEntries(resource, resData.getAccessControlEntries()); 383 changed = true; 384 break; 385 } 386 } 387 return changed; 388 } 389 390 /** 391 * Creates the project used to import module resources and sets it on the CmsObject. 392 * 393 * @param cms the CmsObject to set the project on 394 * @param module the module 395 * @return the created project 396 * @throws CmsException if something goes wrong 397 */ 398 protected CmsProject createAndSetModuleImportProject(CmsObject cms, CmsModule module) throws CmsException { 399 400 CmsProject importProject = cms.createProject( 401 org.opencms.module.Messages.get().getBundle(cms.getRequestContext().getLocale()).key( 402 org.opencms.module.Messages.GUI_IMPORT_MODULE_PROJECT_NAME_1, 403 new Object[] {module.getName()}), 404 org.opencms.module.Messages.get().getBundle(cms.getRequestContext().getLocale()).key( 405 org.opencms.module.Messages.GUI_IMPORT_MODULE_PROJECT_DESC_1, 406 new Object[] {module.getName()}), 407 OpenCms.getDefaultUsers().getGroupAdministrators(), 408 OpenCms.getDefaultUsers().getGroupAdministrators(), 409 CmsProject.PROJECT_TYPE_TEMPORARY); 410 cms.getRequestContext().setCurrentProject(importProject); 411 cms.copyResourceToProject("/"); 412 return importProject; 413 } 414 415 /** 416 * Deletes and publishes resources with ID conflicts. 417 * 418 * @param cms the CMS context to use 419 * @param module the module 420 * @param conflictingIds the conflicting ids 421 * @throws CmsException if something goes wrong 422 * @throws Exception if something goes wrong 423 */ 424 protected void deleteConflictingResources(CmsObject cms, CmsModule module, Map<CmsUUID, CmsUUID> conflictingIds) 425 throws CmsException, Exception { 426 427 CmsProject conflictProject = cms.createProject( 428 "Deletion of conflicting resources for " + module.getName(), 429 "Deletion of conflicting resources for " + module.getName(), 430 OpenCms.getDefaultUsers().getGroupAdministrators(), 431 OpenCms.getDefaultUsers().getGroupAdministrators(), 432 CmsProject.PROJECT_TYPE_TEMPORARY); 433 CmsObject deleteCms = OpenCms.initCmsObject(cms); 434 deleteCms.getRequestContext().setCurrentProject(conflictProject); 435 for (CmsUUID vfsId : conflictingIds.values()) { 436 CmsResource toDelete = deleteCms.readResource(vfsId, CmsResourceFilter.ALL); 437 lock(deleteCms, toDelete); 438 deleteCms.deleteResource(toDelete, CmsResource.DELETE_PRESERVE_SIBLINGS); 439 } 440 OpenCms.getPublishManager().publishProject(deleteCms); 441 OpenCms.getPublishManager().waitWhileRunning(); 442 } 443 444 /** 445 * Parses links for XMLContents etc. 446 * 447 * @param cms the CMS context to use 448 * @throws CmsException if something goes wrong 449 */ 450 protected void parseLinks(CmsObject cms) throws CmsException { 451 452 List<CmsResource> linkParseables = new ArrayList<>(); 453 for (CmsResourceImportData resData : m_moduleData.getResourceData()) { 454 CmsResource importRes = resData.getImportResource(); 455 if ((importRes != null) && m_importIds.contains(importRes.getStructureId()) && isLinkParsable(importRes)) { 456 linkParseables.add(importRes); 457 } 458 } 459 m_report.println(Messages.get().container(Messages.RPT_START_PARSE_LINKS_0), I_CmsReport.FORMAT_HEADLINE); 460 CmsImportVersion10.parseLinks(cms, linkParseables, m_report); 461 m_report.println(Messages.get().container(Messages.RPT_END_PARSE_LINKS_0), I_CmsReport.FORMAT_HEADLINE); 462 } 463 464 /** 465 * Handles the file deletions. 466 * 467 * @param cms the CMS context to use 468 * @param toDelete the resources to delete 469 * 470 * @throws CmsException if something goes wrong 471 */ 472 protected void processDeletions(CmsObject cms, List<CmsResource> toDelete) throws CmsException { 473 474 Collections.sort(toDelete, (a, b) -> b.getRootPath().compareTo(a.getRootPath())); 475 for (CmsResource deleteRes : toDelete) { 476 m_report.print( 477 org.opencms.importexport.Messages.get().container(org.opencms.importexport.Messages.RPT_DELFOLDER_0), 478 I_CmsReport.FORMAT_NOTE); 479 m_report.print( 480 org.opencms.report.Messages.get().container( 481 org.opencms.report.Messages.RPT_ARGUMENT_1, 482 deleteRes.getRootPath())); 483 CmsLock lock = cms.getLock(deleteRes); 484 if (lock.isUnlocked()) { 485 lock(cms, deleteRes); 486 } 487 cms.deleteResource(deleteRes, CmsResource.DELETE_PRESERVE_SIBLINGS); 488 m_report.println( 489 org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0), 490 I_CmsReport.FORMAT_OK); 491 492 } 493 } 494 495 /** 496 * Processes single resource from module import data. 497 * 498 * @param cms the CMS context to use 499 * @param resData the resource data from the module import 500 * @param index index of the current import resource 501 */ 502 protected void processImportResource(CmsObject cms, CmsResourceImportData resData, int index) { 503 504 boolean changed = false; 505 m_report.print( 506 org.opencms.report.Messages.get().container( 507 org.opencms.report.Messages.RPT_ARGUMENT_1, 508 "( " + index + " / " + m_moduleData.getResourceData().size() + " ) "), 509 I_CmsReport.FORMAT_NOTE); 510 m_report.print(Messages.get().container(Messages.RPT_IMPORTING_0), I_CmsReport.FORMAT_NOTE); 511 m_report.print( 512 org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ARGUMENT_1, resData.getPath())); 513 m_report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0)); 514 try { 515 CmsResource oldRes = null; 516 try { 517 if (resData.hasStructureId()) { 518 oldRes = cms.readResource( 519 resData.getResource().getStructureId(), 520 CmsResourceFilter.IGNORE_EXPIRATION); 521 } else { 522 oldRes = cms.readResource(resData.getPath(), CmsResourceFilter.IGNORE_EXPIRATION); 523 } 524 } catch (CmsVfsResourceNotFoundException e) { 525 LOG.debug(e.getLocalizedMessage(), e); 526 } 527 CmsResource currentRes = oldRes; 528 if (oldRes != null) { 529 String oldPath = cms.getSitePath(oldRes); 530 String newPath = resData.getPath(); 531 if (!CmsStringUtil.comparePaths(oldPath, resData.getPath())) { 532 cms.moveResource(oldPath, newPath); 533 changed = true; 534 currentRes = cms.readResource(oldRes.getStructureId(), CmsResourceFilter.IGNORE_EXPIRATION); 535 } 536 } 537 boolean needsImport = true; 538 boolean reducedExport = !resData.hasDateLastModified(); 539 byte[] content = resData.getContent(); 540 if (oldRes != null) { 541 if (!resData.hasStructureId()) { 542 needsImport = false; 543 } else if (oldRes.getState().isUnchanged() 544 && !needToUpdateResourceFields(oldRes, resData.getResource(), reducedExport)) { 545 546 // if resource is changed or new, we don't want to go into this code block 547 // because even if the content / metaadata are the same, we still want the file to be published at the end, 548 // so we import it to add it to the current working project 549 550 if (oldRes.isFile() && (content != null)) { 551 CmsFile file = cms.readFile(oldRes); 552 if (Arrays.equals(file.getContents(), content)) { 553 needsImport = false; 554 } else { 555 LOG.debug("Content mismatch for " + file.getRootPath()); 556 } 557 } else { 558 needsImport = false; 559 } 560 } 561 } 562 if (needsImport || (oldRes == null)) { // oldRes null check is redundant, we just do it to remove the warning in Eclipse 563 currentRes = cms.importResource( 564 resData.getPath(), 565 resData.getResource(), 566 content, 567 new ArrayList<CmsProperty>()); 568 changed = true; 569 m_importIds.add(currentRes.getStructureId()); 570 } else { 571 currentRes = cms.readResource(oldRes.getStructureId(), CmsResourceFilter.ALL); 572 CmsLock lock = cms.getLock(currentRes); 573 if (lock.isUnlocked()) { 574 lock(cms, currentRes); 575 } 576 } 577 resData.setImportResource(currentRes); 578 List<CmsProperty> propsToWrite = compareProperties(cms, resData, currentRes); 579 if (!propsToWrite.isEmpty()) { 580 cms.writePropertyObjects(currentRes, propsToWrite); 581 changed = true; 582 } 583 changed |= updateAcls(cms, resData, currentRes); 584 if (changed) { 585 m_report.println( 586 org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0), 587 I_CmsReport.FORMAT_OK); 588 } else { 589 m_report.println( 590 org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_SKIPPED_0), 591 I_CmsReport.FORMAT_NOTE); 592 } 593 594 } catch (Exception e) { 595 m_report.println(e); 596 LOG.error(e.getLocalizedMessage(), e); 597 598 } 599 } 600 601 /** 602 * Runs the module import script. 603 * 604 * @param cms the CMS context to use 605 * @param module the module for which to run the script 606 */ 607 protected void runImportScript(CmsObject cms, CmsModule module) { 608 609 LOG.info("Executing import script for module " + module.getName()); 610 m_report.println( 611 org.opencms.module.Messages.get().container(org.opencms.module.Messages.RPT_IMPORT_SCRIPT_HEADER_0), 612 I_CmsReport.FORMAT_HEADLINE); 613 String importScript = "echo on\n" + module.getImportScript(); 614 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 615 PrintStream out = new PrintStream(buffer); 616 CmsShell shell = new CmsShell(cms, "${user}@${project}:${siteroot}|${uri}>", null, out, out); 617 shell.execute(importScript); 618 String outputString = buffer.toString(); 619 LOG.info("Shell output for import script was: \n" + outputString); 620 m_report.println( 621 org.opencms.module.Messages.get().container( 622 org.opencms.module.Messages.RPT_IMPORT_SCRIPT_OUTPUT_1, 623 outputString)); 624 } 625 626 /** 627 * Converts access control list to map form, with principal ids as keys.<p> 628 * 629 * @param acl an access control list 630 * @return the map with the access control entries 631 */ 632 Map<CmsUUID, CmsAccessControlEntry> buildAceMap(Collection<CmsAccessControlEntry> acl) { 633 634 if (acl == null) { 635 acl = new ArrayList<>(); 636 } 637 Map<CmsUUID, CmsAccessControlEntry> result = new HashMap<>(); 638 for (CmsAccessControlEntry ace : acl) { 639 result.put(ace.getPrincipal(), ace); 640 } 641 return result; 642 } 643 644 /** 645 * Cleans up temp files. 646 */ 647 private void cleanUp() { 648 649 for (CmsResourceImportData resData : m_moduleData.getResourceData()) { 650 resData.cleanUp(); 651 } 652 } 653 654 /** 655 * Compares properties of an existing resource with those to be imported, and returns a list of properties that need to be updated.<p> 656 * 657 * @param cms the current CMS context 658 * @param resData the resource import data 659 * @param existingResource the existing resource 660 * @return the list of properties that need to be updated 661 * 662 * @throws CmsException if something goes wrong 663 */ 664 private List<CmsProperty> compareProperties( 665 CmsObject cms, 666 CmsResourceImportData resData, 667 CmsResource existingResource) 668 throws CmsException { 669 670 if (existingResource == null) { 671 return Collections.emptyList(); 672 } 673 674 Map<String, CmsProperty> importProps = resData.getProperties(); 675 Map<String, CmsProperty> existingProps = CmsProperty.getPropertyMap( 676 cms.readPropertyObjects(existingResource, false)); 677 Map<String, CmsProperty> propsToWrite = new HashMap<>(); 678 Set<String> keys = new HashSet<>(); 679 keys.addAll(existingProps.keySet()); 680 keys.addAll(importProps.keySet()); 681 682 for (String key : keys) { 683 CmsProperty existingProp = existingProps.get(key); 684 CmsProperty importProp = importProps.get(key); 685 if (existingProp == null) { 686 propsToWrite.put(key, importProp); 687 } else if (importProp == null) { 688 propsToWrite.put(key, new CmsProperty(key, "", "")); 689 } else if (!existingProp.isIdentical(importProp)) { 690 propsToWrite.put(key, importProp); 691 } 692 } 693 return new ArrayList<>(propsToWrite.values()); 694 695 } 696 697 /** 698 * Checks if a resource is link parseable.<P> 699 * 700 * @param importRes the resource to check 701 * @return true if the resource is link parseable 702 * 703 * @throws CmsException if something goes wrong 704 */ 705 private boolean isLinkParsable(CmsResource importRes) throws CmsException { 706 707 int typeId = importRes.getTypeId(); 708 I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(typeId); 709 return type instanceof I_CmsLinkParseable; 710 711 } 712 713 /** 714 * Locks a resource, or steals the lock if it's already locked.<p> 715 * 716 * @param cms the CMS context 717 * @param resource the resource to lock 718 * @throws CmsException if something goes wrong 719 */ 720 private void lock(CmsObject cms, CmsResource resource) throws CmsException { 721 722 CmsLock lock = cms.getLock(resource); 723 if (lock.isUnlocked()) { 724 cms.lockResourceTemporary(resource); 725 } else { 726 cms.changeLock(resource); 727 } 728 } 729 730 /** 731 * Compares the relation (not defined in content) for a resource with those to be imported, and makes 732 * the necessary modifications. 733 * 734 * @param cms the CMS context 735 * @param importResource the resource 736 * @param relations the relations to be imported 737 * 738 * @throws CmsException if something goes wrong 739 */ 740 private void updateRelations(CmsObject cms, CmsResource importResource, List<RelationData> relations) 741 throws CmsException { 742 743 Map<String, CmsRelationType> relTypes = new HashMap<>(); 744 for (CmsRelationType relType : OpenCms.getResourceManager().getRelationTypes()) { 745 relTypes.put(relType.getName(), relType); 746 } 747 Set<CmsRelation> existingRelations = Sets.newHashSet( 748 cms.readRelations(CmsRelationFilter.relationsFromStructureId(importResource.getStructureId()))); 749 List<CmsRelation> noContentRelations = existingRelations.stream().filter( 750 rel -> !rel.getType().isDefinedInContent()).collect(Collectors.toList()); 751 Set<CmsRelation> newRelations = new HashSet<>(); 752 for (RelationData rel : relations) { 753 if (!rel.getType().isDefinedInContent()) { 754 newRelations.add( 755 new CmsRelation( 756 importResource.getStructureId(), 757 importResource.getRootPath(), 758 rel.getTargetId(), 759 rel.getTarget(), 760 rel.getType())); 761 } 762 } 763 if (!newRelations.equals(noContentRelations)) { 764 765 CmsRelationFilter relFilter = CmsRelationFilter.TARGETS.filterNotDefinedInContent(); 766 767 cms.deleteRelationsFromResource(importResource, relFilter); 768 for (CmsRelation newRel : newRelations) { 769 cms.addRelationToResource( 770 importResource, 771 cms.readResource(newRel.getTargetId(), CmsResourceFilter.IGNORE_EXPIRATION), 772 newRel.getType().getName()); 773 774 } 775 } 776 } 777 778}