001/* 002 * Copyright 2019 Anyware Services 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.ametys.plugins.webcontentio.archive; 017 018import java.io.IOException; 019import java.nio.file.Path; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.Iterator; 024import java.util.List; 025import java.util.Map; 026import java.util.Optional; 027import java.util.Set; 028import java.util.stream.Collectors; 029import java.util.stream.Stream; 030import java.util.zip.ZipEntry; 031import java.util.zip.ZipOutputStream; 032 033import javax.jcr.Node; 034import javax.jcr.NodeIterator; 035import javax.jcr.Property; 036import javax.jcr.RepositoryException; 037import javax.jcr.Session; 038import javax.jcr.Value; 039import javax.xml.parsers.ParserConfigurationException; 040import javax.xml.transform.TransformerConfigurationException; 041import javax.xml.transform.sax.TransformerHandler; 042import javax.xml.transform.stream.StreamResult; 043 044import org.apache.avalon.framework.service.ServiceException; 045import org.apache.avalon.framework.service.ServiceManager; 046import org.apache.avalon.framework.service.Serviceable; 047import org.apache.cocoon.xml.AttributesImpl; 048import org.apache.cocoon.xml.XMLUtils; 049import org.apache.commons.lang3.StringUtils; 050import org.apache.commons.math3.util.IntegerSequence.Incrementor; 051import org.xml.sax.Attributes; 052import org.xml.sax.SAXException; 053 054import org.ametys.cms.tag.Tag; 055import org.ametys.cms.tag.TagProvider; 056import org.ametys.cms.tag.TagProviderExtensionPoint; 057import org.ametys.core.util.LambdaUtils; 058import org.ametys.plugins.contentio.archive.ArchiveHandler; 059import org.ametys.plugins.contentio.archive.Archiver; 060import org.ametys.plugins.contentio.archive.Archivers; 061import org.ametys.plugins.contentio.archive.ContentsArchiverHelper; 062import org.ametys.plugins.contentio.archive.DefaultPluginArchiver; 063import org.ametys.plugins.contentio.archive.ImportReport; 064import org.ametys.plugins.contentio.archive.ManifestReaderWriter; 065import org.ametys.plugins.contentio.archive.Merger; 066import org.ametys.plugins.contentio.archive.PartialImport; 067import org.ametys.plugins.contentio.archive.ResourcesArchiverHelper; 068import org.ametys.plugins.explorer.resources.ResourceCollection; 069import org.ametys.plugins.repository.AmetysObjectFactoryExtensionPoint; 070import org.ametys.plugins.repository.AmetysObjectIterable; 071import org.ametys.plugins.repository.AmetysObjectResolver; 072import org.ametys.plugins.repository.ModifiableAmetysObject; 073import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 074import org.ametys.plugins.repository.RepositoryConstants; 075import org.ametys.plugins.repository.TraversableAmetysObject; 076import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 077import org.ametys.plugins.repository.jcr.JCRAmetysObject; 078import org.ametys.runtime.i18n.I18nizableText; 079import org.ametys.runtime.plugin.component.AbstractLogEnabled; 080import org.ametys.web.data.type.ModelItemTypeExtensionPoint; 081import org.ametys.web.repository.page.Page; 082import org.ametys.web.repository.page.Page.PageType; 083import org.ametys.web.repository.page.SitemapElement; 084import org.ametys.web.repository.page.Zone; 085import org.ametys.web.repository.page.ZoneItem; 086import org.ametys.web.repository.page.ZoneItem.ZoneType; 087import org.ametys.web.repository.page.jcr.DefaultPageFactory; 088import org.ametys.web.repository.site.Site; 089import org.ametys.web.repository.site.SiteManager; 090import org.ametys.web.repository.site.SiteType; 091import org.ametys.web.repository.site.SiteTypesExtensionPoint; 092import org.ametys.web.repository.sitemap.Sitemap; 093import org.ametys.web.repository.sitemap.SitemapFactory; 094import org.ametys.web.service.ServiceExtensionPoint; 095 096import com.google.common.collect.Streams; 097 098/** 099 * {@link Archiver} for all sites. 100 */ 101public class SitesArchiver extends AbstractLogEnabled implements Archiver, Serviceable 102{ 103 /** Archiver id. */ 104 public static final String ID = "sites"; 105 106 static final String __SITE_RESOURCES_JCR_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":resources"; 107 static final String __SITE_CONTENTS_JCR_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":contents"; 108 static final String __SITE_PLUGINS_JCR_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":plugins"; 109 110 static final String __COMMON_PREFIX = ID + "/"; 111 static final String __ACL_ZIP_ENTRY_FILENAME = "acl.xml"; 112 113 private static final String __PARTIAL_IMPORT_PREFIX = ID + "/"; 114 115 /** The site manager */ 116 protected SiteManager _siteManager; 117 /** The helper for archiving contents */ 118 protected ContentsArchiverHelper _contentsArchiverHelper; 119 /** The helper for archiving resources */ 120 protected ResourcesArchiverHelper _resourcesArchiverHelper; 121 /** The extension point for {@link SitePluginArchiver}s */ 122 protected SitePluginArchiverExtensionPoint _pluginArchiverExtensionPoint; 123 /** The extension point for {@link TagProvider}s */ 124 protected TagProviderExtensionPoint _tagProviderEP; 125 /** The Ametys Object Resolver */ 126 protected AmetysObjectResolver _resolver; 127 /** The default page factory */ 128 protected DefaultPageFactory _defaultPageFactory; 129 /** The sitemap factory */ 130 protected SitemapFactory _sitemapFactory; 131 /** The extension point for Services */ 132 protected ServiceExtensionPoint _serviceExtensionPoint; 133 /** The extension point for {@link SiteType}s */ 134 protected SiteTypesExtensionPoint _siteTypeEP; 135 /** The page data type extension point */ 136 protected ModelItemTypeExtensionPoint _pageDataTypeExtensionPoint; 137 138 private ManifestReaderWriter _manifestReaderWriter = new SitesArchiverManifestReaderWriter(); 139 140 @Override 141 public void service(ServiceManager manager) throws ServiceException 142 { 143 _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE); 144 _contentsArchiverHelper = (ContentsArchiverHelper) manager.lookup(ContentsArchiverHelper.ROLE); 145 _resourcesArchiverHelper = (ResourcesArchiverHelper) manager.lookup(ResourcesArchiverHelper.ROLE); 146 _pluginArchiverExtensionPoint = (SitePluginArchiverExtensionPoint) manager.lookup(SitePluginArchiverExtensionPoint.ROLE); 147 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 148 _tagProviderEP = (TagProviderExtensionPoint) manager.lookup(TagProviderExtensionPoint.ROLE); 149 AmetysObjectFactoryExtensionPoint ametysObjectFactoryEP = (AmetysObjectFactoryExtensionPoint) manager.lookup(AmetysObjectFactoryExtensionPoint.ROLE); 150 _defaultPageFactory = (DefaultPageFactory) ametysObjectFactoryEP.getExtension(DefaultPageFactory.class.getName()); 151 _sitemapFactory = (SitemapFactory) ametysObjectFactoryEP.getExtension(SitemapFactory.class.getName()); 152 _serviceExtensionPoint = (ServiceExtensionPoint) manager.lookup(ServiceExtensionPoint.ROLE); 153 _siteTypeEP = (SiteTypesExtensionPoint) manager.lookup(SiteTypesExtensionPoint.ROLE); 154 _pageDataTypeExtensionPoint = (ModelItemTypeExtensionPoint) manager.lookup(ModelItemTypeExtensionPoint.ROLE_PAGE_DATA); 155 } 156 157 @Override 158 public ManifestReaderWriter getManifestReaderWriter() 159 { 160 return _manifestReaderWriter; 161 } 162 163 private class SitesArchiverManifestReaderWriter implements ManifestReaderWriter 164 { 165 @Override 166 public Object getData() 167 { 168 return _siteManager.getSiteNames(); 169 } 170 171 @Override 172 public Stream<PartialImport> toPartialImports(Object data) 173 { 174 return Stream.concat( 175 _allSitesToPartialImports(), 176 _sitesDataToPartialImports(data)); 177 } 178 179 private Stream<PartialImport> _allSitesToPartialImports() 180 { 181 return Stream.of(PartialImport.of(ID, new I18nizableText("plugin.web-contentio", "PLUGINS_WEB_CONTENTIO_ARCHIVE_IMPORT_SITE_ARCHIVER_OPTION_ALL"))); // for importing all sites 182 } 183 184 @SuppressWarnings("synthetic-access") 185 private Stream<PartialImport> _sitesDataToPartialImports(Object data) 186 { 187 return Optional.ofNullable(data) 188 .filter(Collection.class::isInstance) 189 .map(this::_castToCollection) 190 .orElseGet(() -> 191 { 192 getLogger().warn("Unexpected manifest data '{}', we would expect an array representing the site names. The ZIP archive probably comes from a different version of Ametys.", data); 193 return Collections.emptySet(); 194 }) 195 .stream() 196 .sorted() // sort by site name 197 .map(this::_toPartialImport); 198 } 199 200 private Collection<String> _castToCollection(Object data) 201 { 202 return Collection.class.cast(data); 203 } 204 205 private PartialImport _toPartialImport(String siteName) 206 { 207 String key = __PARTIAL_IMPORT_PREFIX + siteName; 208 return PartialImport.of(key, _toPartialImportLabel(siteName)); 209 } 210 211 private I18nizableText _toPartialImportLabel(String siteName) 212 { 213 // "Site 'wwww'" 214 return new I18nizableText("plugin.web-contentio", "PLUGINS_WEB_CONTENTIO_ARCHIVE_IMPORT_SITE_ARCHIVER_OPTION_ONE_SITE", Map.of("siteName", new I18nizableText(siteName))); 215 } 216 } 217 218 @Override 219 public void export(ZipOutputStream zos) throws IOException 220 { 221 zos.putNextEntry(new ZipEntry(__COMMON_PREFIX)); // even if there is no site, at least export the sites folder 222 223 try (AmetysObjectIterable<Site> sites = _siteManager.getSites()) 224 { 225 for (Site site : sites) 226 { 227 getLogger().info("Processing site '{}' for archiving", site.getName()); 228 _exportSite(site, zos); 229 } 230 } 231 } 232 233 private void _exportSite(Site site, ZipOutputStream zos) throws IOException 234 { 235 String siteName = site.getName(); 236 String path = Archivers.getHashedPath(siteName); 237 String prefix = __COMMON_PREFIX + path + "/" + siteName; 238 239 // export site's own data 240 ZipEntry siteEntry = new ZipEntry(prefix + "/" + siteName + ".xml"); 241 zos.putNextEntry(siteEntry); 242 243 try 244 { 245 TransformerHandler contentHandler = Archivers.newTransformerHandler(); 246 contentHandler.setResult(new StreamResult(zos)); 247 248 contentHandler.startDocument(); 249 Attributes attrs = _getSiteAttributes(site); 250 XMLUtils.startElement(contentHandler, "site", attrs); 251 site.dataToSAX(contentHandler); 252 XMLUtils.endElement(contentHandler, "site"); 253 contentHandler.endDocument(); 254 } 255 catch (SAXException | TransformerConfigurationException e) 256 { 257 throw new RuntimeException("Unable to SAX site '" + site.getPath() + "' for archiving", e); 258 } 259 260 // export site's resources 261 ResourceCollection rootResources = site.getChild(__SITE_RESOURCES_JCR_NODE_NAME); 262 _resourcesArchiverHelper.exportCollection(rootResources, zos, prefix + "/resources/"); 263 264 // export site's binaries 265 Archivers.exportBinaries(site, zos, prefix + "/"); 266 267 // export all site's contents 268 TraversableAmetysObject rootContents = site.getChild(__SITE_CONTENTS_JCR_NODE_NAME); 269 _contentsArchiverHelper.exportContents(prefix + "/contents/", rootContents, zos); 270 271 // export site's plugins data 272 try 273 { 274 _getPluginNodes(site) 275 .forEachRemaining(LambdaUtils.wrapConsumer(pluginNode -> _exportPlugin(site, pluginNode, zos, prefix))); 276 } 277 catch (RepositoryException e) 278 { 279 throw new IllegalArgumentException("Unable to archive plugins for site " + siteName, e); 280 } 281 282 _exportSitemaps(site, zos, prefix, siteName); 283 } 284 285 private Attributes _getSiteAttributes(Site site) 286 { 287 AttributesImpl attrs = new AttributesImpl(); 288 attrs.addCDATAAttribute("id", site.getId()); 289 attrs.addCDATAAttribute("type", site.getType()); 290 return attrs; 291 } 292 293 static Node _getAllPluginsNode(Site site) throws RepositoryException 294 { 295 return site.getNode() 296 .getNode(__SITE_PLUGINS_JCR_NODE_NAME); 297 } 298 299 private static Iterator<Node> _getPluginNodes(Site site) throws RepositoryException 300 { 301 return _getAllPluginsNode(site) 302 .getNodes(); 303 } 304 305 SitePluginArchiver _retrieveSitePluginArchiver(String pluginName) 306 { 307 SitePluginArchiver sitePluginArchiver = _pluginArchiverExtensionPoint.getExtension(pluginName); 308 if (sitePluginArchiver == null) 309 { 310 // there's no specific exporter for this plugin, let's fallback to the default export 311 sitePluginArchiver = _pluginArchiverExtensionPoint.getExtension(DefaultPluginArchiver.EXTENSION_ID); 312 313 if (sitePluginArchiver == null) 314 { 315 throw new IllegalStateException("There sould be a '__default' extension to SitePluginArchiverExtensionPoint. Please check your excluded features."); 316 } 317 } 318 319 return sitePluginArchiver; 320 } 321 322 private void _exportPlugin(Site site, Node pluginNode, ZipOutputStream zos, String prefix) throws Exception 323 { 324 String pluginName = pluginNode.getName(); 325 SitePluginArchiver sitePluginArchiver = _retrieveSitePluginArchiver(pluginName); 326 getLogger().info("Processing plugin '{}' for site '{}' for archiving at {} with archiver '{}'", pluginName, site.getName(), pluginNode, sitePluginArchiver.getClass().getName()); 327 sitePluginArchiver.export(site, pluginName, pluginNode, zos, prefix + "/plugins/" + pluginName); 328 } 329 330 private void _exportSitemaps(Site site, ZipOutputStream zos, String prefix, String siteName) throws IOException 331 { 332 // export all stored pages 333 try (AmetysObjectIterable<Sitemap> sitemaps = site.getSitemaps()) 334 { 335 for (Sitemap sitemap : sitemaps) 336 { 337 _exportSitemap(sitemap, zos, prefix); 338 } 339 } 340 catch (RepositoryException e) 341 { 342 throw new IllegalArgumentException("Unable to archive pages for site " + siteName, e); 343 } 344 } 345 346 private void _exportSitemap(Sitemap sitemap, ZipOutputStream zos, String prefix) throws IOException, RepositoryException 347 { 348 String sitemapPrefix = prefix + "/pages/" + sitemap.getName(); 349 zos.putNextEntry(new ZipEntry(sitemapPrefix + "/")); // even if there is no page, at least export the sitemap folder 350 _exportSitemapData(sitemap, zos, sitemapPrefix); 351 352 Stream<Node> nodes = Streams.stream(_getSitemapChildrenNodes(sitemap)); 353 Incrementor orderIncrementor = Incrementor.create() 354 .withStart(0) 355 .withMaximalCount(Integer.MAX_VALUE); 356 nodes.filter(LambdaUtils.wrapPredicate(node -> node.isNodeType("ametys:page"))) 357 .forEachOrdered(LambdaUtils.wrapConsumer(pageNode -> 358 { 359 _exportPage(pageNode, zos, sitemapPrefix, orderIncrementor.getCount()); 360 orderIncrementor.increment(); 361 })); 362 } 363 364 private void _exportSitemapData(Sitemap sitemap, ZipOutputStream zos, String sitemapPrefix) throws RepositoryException, IOException 365 { 366 ZipEntry siteEntry = new ZipEntry(sitemapPrefix + ".xml"); 367 zos.putNextEntry(siteEntry); 368 369 try 370 { 371 TransformerHandler contentHandler = Archivers.newTransformerHandler(); 372 contentHandler.setResult(new StreamResult(zos)); 373 374 contentHandler.startDocument(); 375 Attributes attrs = _getSitemapAttributes(sitemap); 376 XMLUtils.startElement(contentHandler, "sitemap", attrs); 377 378 // internal properties (virtual, ...) 379 _saxAmetysInternalProperties(sitemap, contentHandler); 380 381 XMLUtils.startElement(contentHandler, "attributes"); 382 sitemap.dataToSAX(contentHandler); 383 XMLUtils.endElement(contentHandler, "attributes"); 384 XMLUtils.endElement(contentHandler, "sitemap"); 385 contentHandler.endDocument(); 386 } 387 catch (SAXException | TransformerConfigurationException e) 388 { 389 throw new RuntimeException("Unable to SAX sitemap '" + sitemap.getPath() + "' for archiving", e); 390 } 391 392 Archivers.exportAcl(sitemap.getNode(), zos, ArchiveHandler.METADATA_PREFIX + sitemapPrefix + "/" + __ACL_ZIP_ENTRY_FILENAME); 393 } 394 395 private Attributes _getSitemapAttributes(Sitemap sitemap) 396 { 397 AttributesImpl attrs = new AttributesImpl(); 398 attrs.addCDATAAttribute("name", sitemap.getName()); 399 attrs.addCDATAAttribute("id", sitemap.getId()); 400 return attrs; 401 } 402 403 private static Iterator<Node> _getSitemapChildrenNodes(Sitemap sitemap) throws RepositoryException 404 { 405 return sitemap.getNode().getNodes(); 406 } 407 408 private void _exportPage(Node pageNode, ZipOutputStream zos, String prefix, int order) throws RepositoryException, IOException 409 { 410 String pageName = pageNode.getName(); 411 Page page = _resolver.resolve(pageNode, false); 412 413 String pagePrefix = prefix + "/" + pageName; 414 415 ZipEntry siteEntry = new ZipEntry(pagePrefix + ".xml"); 416 zos.putNextEntry(siteEntry); 417 418 try 419 { 420 TransformerHandler contentHandler = Archivers.newTransformerHandler(); 421 contentHandler.setResult(new StreamResult(zos)); 422 423 contentHandler.startDocument(); 424 Attributes attrs = _getPageAttributes(page, order); 425 426 XMLUtils.startElement(contentHandler, "page", attrs); 427 428 // internal properties (virtual, ...) 429 _saxAmetysInternalProperties(page, contentHandler); 430 431 XMLUtils.startElement(contentHandler, "attributes"); 432 page.dataToSAX(contentHandler); 433 XMLUtils.endElement(contentHandler, "attributes"); 434 435 // Tags 436 _saxTags(page, contentHandler); 437 438 XMLUtils.startElement(contentHandler, "pageContents"); 439 440 _saxZones(page, contentHandler); 441 442 XMLUtils.endElement(contentHandler, "pageContents"); 443 444 XMLUtils.endElement(contentHandler, "page"); 445 contentHandler.endDocument(); 446 } 447 catch (SAXException | TransformerConfigurationException e) 448 { 449 throw new RuntimeException("Unable to SAX page '" + page.getPath() + "' for archiving", e); 450 } 451 catch (RuntimeException e) 452 { 453 throw new RuntimeException("Unable to process Page for archiving: " + page.getId(), e); 454 } 455 456 _saxAttachments(page, pagePrefix, zos); 457 458 Archivers.exportAcl(pageNode, zos, ArchiveHandler.METADATA_PREFIX + pagePrefix + "/" + __ACL_ZIP_ENTRY_FILENAME); 459 460 Stream<Node> nodes = Streams.stream(_getPageChildrenNodes(pageNode)); 461 Incrementor orderIncrementor = Incrementor.create() 462 .withStart(0) 463 .withMaximalCount(Integer.MAX_VALUE); 464 nodes.filter(LambdaUtils.wrapPredicate(node -> node.isNodeType("ametys:page"))) 465 .forEachOrdered(LambdaUtils.wrapConsumer(node -> 466 { 467 _exportPage(node, zos, pagePrefix, orderIncrementor.getCount()); 468 orderIncrementor.increment(); 469 })); 470 } 471 472 private Attributes _getPageAttributes(Page page, int order) 473 { 474 AttributesImpl attrs = new AttributesImpl(); 475 attrs.addCDATAAttribute("title", page.getTitle()); 476 attrs.addCDATAAttribute("long-title", page.getLongTitle()); 477 attrs.addCDATAAttribute("id", page.getId()); 478 attrs.addCDATAAttribute("type", page.getType().toString()); 479 attrs.addCDATAAttribute("order", Integer.toString(order)); 480 481 if (page.getType() == PageType.LINK) 482 { 483 attrs.addCDATAAttribute("url", page.getURL()); 484 attrs.addCDATAAttribute("urlType", page.getURLType().toString()); 485 } 486 else if (page.getType() == PageType.CONTAINER) 487 { 488 attrs.addCDATAAttribute("template", page.getTemplate()); 489 } 490 491 return attrs; 492 } 493 494 private void _saxAmetysInternalProperties(SitemapElement sitemapElement, TransformerHandler contentHandler) throws SAXException 495 { 496 XMLUtils.startElement(contentHandler, "internal"); 497 if (sitemapElement instanceof JCRAmetysObject) 498 { 499 Node node = ((JCRAmetysObject) sitemapElement).getNode(); 500 try 501 { 502 _saxInternalPropeties(node, contentHandler, AmetysObjectResolver.VIRTUAL_PROPERTY); 503 } 504 catch (RepositoryException e) 505 { 506 throw new SAXException(e); 507 } 508 } 509 XMLUtils.endElement(contentHandler, "internal"); 510 } 511 512 private void _saxInternalPropeties(Node node, TransformerHandler contentHandler, String... properties) throws RepositoryException, SAXException 513 { 514 for (String propertyName : properties) 515 { 516 if (node.hasProperty(propertyName)) 517 { 518 Property property = node.getProperty(propertyName); 519 for (Value value : property.getValues()) 520 { 521 String propertyValue = value.getString(); 522 XMLUtils.createElement(contentHandler, propertyName, propertyValue); 523 } 524 } 525 } 526 } 527 528 private void _saxTags(Page page, TransformerHandler contentHandler) throws SAXException 529 { 530 XMLUtils.startElement(contentHandler, "tags"); 531 Set<String> tags = page.getTags(); 532 for (String tagName : tags) 533 { 534 Map<String, Object> contextParameters = new HashMap<>(); 535 contextParameters.put("siteName", page.getSiteName()); 536 537 Tag tag = _tagProviderEP.getTag(tagName, contextParameters); 538 539 if (tag != null) 540 { 541 // tag may be null if it has been registered on the page and then removed from the application 542 XMLUtils.createElement(contentHandler, tagName); 543 } 544 } 545 XMLUtils.endElement(contentHandler, "tags"); 546 } 547 548 private void _saxAttachments(Page page, String pagePrefix, ZipOutputStream zos) throws IOException 549 { 550 // Put page attachments under /_metadata 551 // It means that metadata of the attachments will be under /_metadata/_metadata 552 String prefix = ArchiveHandler.METADATA_PREFIX + pagePrefix + "/_attachments/"; 553 _resourcesArchiverHelper.exportCollection(page.getRootAttachments(), zos, prefix); 554 } 555 556 private void _saxZones(Page page, TransformerHandler contentHandler) throws SAXException 557 { 558 for (Zone zone : page.getZones()) 559 { 560 _saxZone(zone, contentHandler); 561 } 562 } 563 564 private void _saxZone(Zone zone, TransformerHandler contentHandler) throws SAXException 565 { 566 try 567 { 568 String zoneName = zone.getName(); 569 570 AttributesImpl zoneAttrs = new AttributesImpl(); 571 zoneAttrs.addCDATAAttribute("name", zoneName); 572 XMLUtils.startElement(contentHandler, "zone", zoneAttrs); 573 574 XMLUtils.startElement(contentHandler, "attributes"); 575 zone.dataToSAX(contentHandler); 576 XMLUtils.endElement(contentHandler, "attributes"); 577 578 AmetysObjectIterable<? extends ZoneItem> zoneItems = zone.getZoneItems(); 579 _saxZoneItems(zoneItems, contentHandler); 580 581 XMLUtils.endElement(contentHandler, "zone"); 582 } 583 catch (RuntimeException e) 584 { 585 throw new RuntimeException("Unable to process Zone for archiving: " + zone.getId(), e); 586 } 587 } 588 589 private void _saxZoneItems(AmetysObjectIterable<? extends ZoneItem> zoneItems, TransformerHandler contentHandler) throws SAXException 590 { 591 for (ZoneItem zoneItem : zoneItems) 592 { 593 try 594 { 595 _saxZoneItem(zoneItem, contentHandler); 596 } 597 catch (RuntimeException e) 598 { 599 throw new RuntimeException("Unable to process ZoneItem for archiving: " + zoneItem.getId(), e); 600 } 601 } 602 } 603 604 private void _saxZoneItem(ZoneItem zoneItem, TransformerHandler contentHandler) throws SAXException 605 { 606 ZoneType zoneType = zoneItem.getType(); 607 608 Attributes zoneItemAttrs = _getZoneItemAttributes(zoneItem, zoneType); 609 610 XMLUtils.startElement(contentHandler, "zoneItem", zoneItemAttrs); 611 612 XMLUtils.startElement(contentHandler, "attributes"); 613 zoneItem.dataToSAX(contentHandler); 614 XMLUtils.endElement(contentHandler, "attributes"); 615 616 if (zoneType == ZoneType.SERVICE) 617 { 618 ModelAwareDataHolder serviceParameters = _getServiceParameters(zoneItem); 619 if (serviceParameters != null) 620 { 621 XMLUtils.startElement(contentHandler, "serviceParameters"); 622 serviceParameters.dataToSAX(contentHandler); 623 XMLUtils.endElement(contentHandler, "serviceParameters"); 624 } 625 } 626 627 XMLUtils.endElement(contentHandler, "zoneItem"); 628 } 629 630 private ModelAwareDataHolder _getServiceParameters(ZoneItem zoneItem) 631 { 632 try 633 { 634 return zoneItem.getServiceParameters(); 635 } 636 catch (Exception e) 637 { 638 getLogger().error("Cannot get service parameters for ZoneItem \"{}\"", zoneItem, e); 639 return null; 640 } 641 } 642 643 private Attributes _getZoneItemAttributes(ZoneItem zoneItem, ZoneType zoneType) 644 { 645 AttributesImpl zoneItemAttrs = new AttributesImpl(); 646 zoneItemAttrs.addCDATAAttribute("type", zoneType.toString()); 647 648 if (zoneType == ZoneType.CONTENT) 649 { 650 zoneItemAttrs.addCDATAAttribute("contentId", zoneItem.getContent().getId()); 651 652 String viewName = zoneItem.getViewName(); 653 if (viewName != null) 654 { 655 zoneItemAttrs.addCDATAAttribute("viewName", viewName); 656 } 657 } 658 else if (zoneType == ZoneType.SERVICE) 659 { 660 zoneItemAttrs.addCDATAAttribute("serviceId", zoneItem.getServiceId()); 661 } 662 663 return zoneItemAttrs; 664 } 665 666 private static Iterator<Node> _getPageChildrenNodes(Node pageNode) throws RepositoryException 667 { 668 return pageNode.getNodes(); 669 } 670 671 @Override 672 public List<I18nizableText> additionalSuccessImportMail() 673 { 674 return List.of( 675 new I18nizableText("plugin.web-contentio", "PLUGINS_WEB_CONTENTIO_ARCHIVE_IMPORT_SITE_ARCHIVER_MAIL_ADDITIONAL_BODY_REBUILD_LIVE"), 676 new I18nizableText("plugin.web-contentio", "PLUGINS_WEB_CONTENTIO_ARCHIVE_IMPORT_SITE_ARCHIVER_MAIL_ADDITIONAL_BODY_SITE_TOOL") 677 ); 678 } 679 680 @Override 681 public Collection<String> managedPartialImports(Collection<String> partialImports) 682 { 683 if (partialImports.contains(ID)) 684 { 685 // All sites 686 return Collections.singletonList(ID); 687 } 688 else 689 { 690 // Return a Collection (can be empty) of sites to be imported 691 return partialImports.stream() 692 .filter(partialImport -> partialImport.startsWith(__PARTIAL_IMPORT_PREFIX)) 693 .collect(Collectors.toList()); 694 } 695 } 696 697 @Override 698 public ImportReport partialImport(Path zipPath, Collection<String> partialImports, Merger merger, boolean deleteBefore) throws IOException 699 { 700 ModifiableTraversableAmetysObject siteRoot = _siteManager.getRoot(); 701 if (deleteBefore && siteRoot instanceof JCRAmetysObject) 702 { 703 _deleteBeforePartialImport(siteRoot, partialImports); 704 } 705 706 ImportReport result = _partialImport(zipPath, partialImports, merger, siteRoot); 707 _saveImported(siteRoot); 708 return result; 709 } 710 711 private void _deleteBeforePartialImport(ModifiableTraversableAmetysObject siteRoot, Collection<String> partialImports) throws IOException 712 { 713 if (!(siteRoot instanceof JCRAmetysObject)) 714 { 715 return; 716 } 717 718 JCRAmetysObject jcrSiteRoot = (JCRAmetysObject) siteRoot; 719 if (partialImports.contains(ID)) 720 { 721 _deleteSiteRootBeforePartialImport(jcrSiteRoot); 722 } 723 else 724 { 725 for (String siteName : _retrieveSiteNames(partialImports)) 726 { 727 _deleteSiteBeforePartialImport(siteName); 728 } 729 } 730 } 731 732 private void _deleteSiteRootBeforePartialImport(JCRAmetysObject siteRoot) throws IOException 733 { 734 try 735 { 736 Node rootNode = siteRoot.getNode(); 737 NodeIterator rootChildren = rootNode.getNodes(); 738 while (rootChildren.hasNext()) 739 { 740 rootChildren.nextNode().remove(); 741 } 742 rootNode.getSession().save(); 743 } 744 catch (RepositoryException e) 745 { 746 throw new IOException(e); 747 } 748 } 749 750 private void _deleteSiteBeforePartialImport(String siteName) throws IOException 751 { 752 if (_siteManager.hasSite(siteName)) 753 { 754 Node siteNode = _siteManager.getSite(siteName).getNode(); 755 try 756 { 757 Session parentNodeSession = siteNode.getParent().getSession(); 758 siteNode.remove(); 759 parentNodeSession.save(); 760 } 761 catch (RepositoryException e) 762 { 763 throw new IOException(e); 764 } 765 } 766 } 767 768 private Collection<String> _retrieveSiteNames(Collection<String> partialImports) 769 { 770 return partialImports.stream() 771 .map(partialImport -> StringUtils.substringAfter(partialImport, __PARTIAL_IMPORT_PREFIX)) 772 .collect(Collectors.toList()); 773 } 774 775 private ImportReport _partialImport(Path zipPath, Collection<String> partialImports, Merger merger, ModifiableTraversableAmetysObject siteRoot) throws IOException 776 { 777 try 778 { 779 var importer = new SiteImporter(this, siteRoot, zipPath, merger, getLogger()); 780 if (partialImports.contains(ID)) 781 { 782 importer.importSites(); 783 } 784 else 785 { 786 for (String siteName : _retrieveSiteNames(partialImports)) 787 { 788 importer.importSite(siteName); 789 } 790 } 791 return importer._report; 792 } 793 catch (ParserConfigurationException e) 794 { 795 throw new IOException(e); 796 } 797 } 798 799 private void _saveImported(ModifiableAmetysObject siteRoot) 800 { 801 if (siteRoot.needsSave()) 802 { 803 getLogger().warn(Archivers.WARN_MESSAGE_ROOT_HAS_PENDING_CHANGES, siteRoot); 804 siteRoot.saveChanges(); 805 } 806 } 807}