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