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