001/*
002 *  Copyright 2020 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.io.InputStream;
020import java.nio.file.Path;
021import java.util.Comparator;
022import java.util.List;
023import java.util.Map;
024import java.util.Optional;
025import java.util.function.Function;
026import java.util.stream.Collectors;
027import java.util.stream.IntStream;
028
029import javax.jcr.Node;
030import javax.jcr.RepositoryException;
031import javax.xml.parsers.DocumentBuilder;
032import javax.xml.transform.TransformerException;
033
034import org.apache.commons.lang3.StringUtils;
035import org.apache.commons.lang3.tuple.Pair;
036import org.apache.xpath.XPathAPI;
037import org.apache.xpath.objects.XObject;
038import org.slf4j.Logger;
039import org.w3c.dom.Document;
040import org.w3c.dom.Element;
041import org.w3c.dom.NodeList;
042import org.xml.sax.SAXException;
043
044import org.ametys.cms.repository.Content;
045import org.ametys.core.util.LambdaUtils;
046import org.ametys.plugins.contentio.archive.ArchiveHandler;
047import org.ametys.plugins.contentio.archive.Archivers;
048import org.ametys.plugins.contentio.archive.Archivers.AmetysObjectNotImportedException;
049import org.ametys.plugins.contentio.archive.ImportGlobalFailException;
050import org.ametys.plugins.contentio.archive.ImportReport;
051import org.ametys.plugins.contentio.archive.ImportReport.ImportError;
052import org.ametys.plugins.contentio.archive.Merger;
053import org.ametys.plugins.contentio.archive.UnitaryImporter;
054import org.ametys.plugins.contentio.archive.ZipEntryHelper;
055import org.ametys.plugins.repository.data.extractor.xml.ModelAwareXMLValuesExtractor;
056import org.ametys.plugins.repository.data.extractor.xml.ModelLessXMLValuesExtractor;
057import org.ametys.plugins.repository.data.extractor.xml.XMLValuesExtractorAdditionalDataGetter;
058import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder;
059import org.ametys.plugins.repository.jcr.DefaultTraversableAmetysObject;
060import org.ametys.plugins.repository.jcr.JCRAmetysObject;
061import org.ametys.runtime.model.View;
062import org.ametys.web.repository.page.ModifiablePage;
063import org.ametys.web.repository.page.ModifiableZone;
064import org.ametys.web.repository.page.ModifiableZoneItem;
065import org.ametys.web.repository.page.Page.LinkType;
066import org.ametys.web.repository.page.Page.PageType;
067import org.ametys.web.repository.page.PagesContainer;
068import org.ametys.web.repository.page.ZoneItem.ZoneType;
069import org.ametys.web.repository.page.jcr.DefaultPage;
070import org.ametys.web.repository.site.Site;
071import org.ametys.web.repository.sitemap.Sitemap;
072import org.ametys.web.service.Service;
073
074import com.google.common.io.MoreFiles;
075
076class SitemapImporter
077{
078    final ImportReport _report = new ImportReport();
079    private final SitesArchiver _siteArchiver;
080    private final Site _site;
081    private final Path _sitemapFolderPath;
082    private final Path _sitemapFilePath;
083    private final Path _zipArchivePath;
084    private final Merger _merger;
085    private final Logger _logger;
086    private final DocumentBuilder _builder;
087    private Sitemap _sitemap;
088    private final UnitarySitemapImporter _unitarySitemapImporter = new UnitarySitemapImporter();
089    
090    SitemapImporter(SitesArchiver siteArchiver, Site site, Path sitemapPath, Path zipArchivePath, Merger merger, Logger logger, DocumentBuilder builder)
091    {
092        _siteArchiver = siteArchiver;
093        _site = site;
094        _sitemapFolderPath = sitemapPath;
095        _sitemapFilePath = _sitemapFolderPath.resolveSibling(_sitemapFolderPath.getFileName() + ".xml");
096        _zipArchivePath = zipArchivePath;
097        _merger = merger;
098        _logger = logger;
099        _builder = builder;
100    }
101    
102    private class UnitarySitemapImporter implements UnitaryImporter<Sitemap>
103    {
104        @Override
105        public String objectNameForLogs()
106        {
107            return "Sitemap";
108        }
109
110        @Override
111        public Document getPropertiesXml(Path zipEntryPath) throws Exception
112        {
113            return _getPropertiesXml(zipEntryPath);
114        }
115
116        @Override
117        public String retrieveId(Document propertiesXml) throws Exception
118        {
119            return Archivers.xpathEvalNonEmpty("sitemap/@id", propertiesXml);
120        }
121
122        @Override
123        public Sitemap create(Path zipEntryPath, String id, Document propertiesXml) throws AmetysObjectNotImportedException, ImportGlobalFailException, Exception
124        {
125            return _createChildSitemap(id, propertiesXml);
126        }
127
128        @Override
129        public ImportReport getReport()
130        {
131            return _report;
132        }
133    }
134    
135    private class UnitaryPageImporter implements UnitaryImporter<Node>
136    {
137        private Document _propertiesXml;
138        
139        UnitaryPageImporter(Document propertiesXml)
140        {
141            _propertiesXml = propertiesXml;
142        }
143
144        @Override
145        public String objectNameForLogs()
146        {
147            return "Page";
148        }
149
150        @Override
151        public Document getPropertiesXml(Path zipEntryPath) throws Exception
152        {
153            return _propertiesXml;
154        }
155
156        @Override
157        public String retrieveId(Document propertiesXml) throws Exception
158        {
159            return Archivers.xpathEvalNonEmpty("page/@id", propertiesXml);
160        }
161
162        @Override
163        public Node create(Path zipEntryPath, String id, Document propertiesXml) throws AmetysObjectNotImportedException, Exception
164        {
165            Node pageNode = _createPage(zipEntryPath, id, propertiesXml);
166            _createPageAcl(pageNode, zipEntryPath);
167            Archivers.unitarySave(pageNode, _logger);
168            return pageNode;
169        }
170        
171        @Override
172        public ImportReport getReport()
173        {
174            return _report;
175        }
176    }
177    
178    void importSitemap() throws RepositoryException, IOException
179    {
180        boolean sitemapImported = _fillSitemap();
181        if (sitemapImported)
182        {
183            _importAllPages();
184        }
185    }
186    
187    private boolean _fillSitemap() throws RepositoryException, IOException
188    {
189        Optional<Sitemap> optionalSitemap = _unitarySitemapImporter.unitaryImport(_zipArchivePath, _sitemapFilePath, _merger, _logger);
190        if (optionalSitemap.isEmpty())
191        {
192            // potentially, sitemap was not imported => it was already logged in error level
193            // but fail, do not import pages
194            return false;
195        }
196        
197        _sitemap = optionalSitemap.get();
198        _createSitemapAcl();
199        try
200        {
201            Archivers.unitarySave(_sitemap.getNode(), _logger);
202            return true;
203        }
204        catch (AmetysObjectNotImportedException e)
205        {
206            // potentially, sitemap was not imported => it was already logged in error level
207            // but fail, do not import pages
208            return false;
209        }
210    }
211    
212    private Sitemap _createChildSitemap(String id, Document propertiesXml) throws AmetysObjectNotImportedException, Exception
213    {
214        String sitemapName = Archivers.xpathEvalNonEmpty("sitemap/@name", propertiesXml);
215        _logger.info("Creating a Sitemap object for '{}' file (lang={})", _sitemapFilePath, sitemapName);
216        String uuid = StringUtils.substringAfter(id, "://");
217        Node sitemapNode = _createChildSitemap(uuid, sitemapName);
218        Sitemap sitemap = _resolveSitemap(sitemapNode);
219        _setSitemapAttributes(sitemap, propertiesXml);
220        Archivers.unitarySave(sitemapNode, _logger);
221        
222        return sitemap;
223    }
224    
225    private Node _createChildSitemap(String uuid, String sitemapName) throws RepositoryException
226    {
227        // Create a Node with JCR primary type "sitemap"
228        // But then call 'replaceNodeWithDesiredUuid' to have it with the desired UUID (srcNode will be removed)
229        Sitemap srcSitemap = _site.addSitemap(sitemapName);
230        Node srcNode = srcSitemap.getNode();
231        Node nodeWithDesiredUuid = Archivers.replaceNodeWithDesiredUuid(srcNode, uuid);
232        return nodeWithDesiredUuid;
233    }
234    
235    private Sitemap _resolveSitemap(Node node)
236    {
237        return _siteArchiver._sitemapFactory.getAmetysObject(node, null);
238    }
239    
240    private void _setSitemapAttributes(Sitemap sitemap, Document propertiesXml) throws TransformerException, RepositoryException
241    {
242        _setAmetysInternalProperties(sitemap, propertiesXml, "sitemap");
243    }
244    
245    private void _createSitemapAcl() throws RepositoryException, IOException
246    {
247        Node node = _sitemap.getNode();
248        String zipEntryPath = new StringBuilder(ArchiveHandler.METADATA_PREFIX)
249                .append(StringUtils.strip(_sitemapFolderPath.toString(), "/"))
250                .append("/")
251                .append(SitesArchiver.__ACL_ZIP_ENTRY_FILENAME)
252                .toString();
253        _logger.debug("Trying to import ACL node for Sitemap '{}', from ACL XML file '{}', if it exists", node, zipEntryPath);
254        Archivers.importAcl(node, _zipArchivePath, _merger, zipEntryPath, _logger);
255    }
256    
257    private void _createPageAcl(Node pageNode, Path pagePath) throws RepositoryException, IOException
258    {
259        String zipEntryPath = new StringBuilder(ArchiveHandler.METADATA_PREFIX)
260                .append(StringUtils.strip(pagePath.getParent().toString(), "/"))
261                .append("/")
262                .append(MoreFiles.getNameWithoutExtension(pagePath))
263                .append("/")
264                .append(SitesArchiver.__ACL_ZIP_ENTRY_FILENAME)
265                .toString();
266        _logger.debug("Trying to import ACL node for Page '{}', from ACL XML file '{}', if it exists", pageNode, zipEntryPath);
267        Archivers.importAcl(pageNode, _zipArchivePath, _merger, zipEntryPath, _logger);
268    }
269    
270    private void _importAllPages() throws IOException
271    {
272        Pair<Path, Document>[] pageXmlFiles = ZipEntryHelper.zipFileTree(
273            _zipArchivePath, 
274            Optional.of(_sitemapFolderPath.toString()), 
275            (p, attrs) -> attrs.isRegularFile())
276                .map(path -> Pair.of(path, _getNullablePagePropertiesXml(path)))
277                // filter out XML properties files which are not present (likely due to an error reading them)
278                .filter(pathAndXml -> pathAndXml.getRight() != null)
279                .sorted(_pageComparator())
280                .toArray(Pair[]::new);
281        
282        for (Pair<Path, Document> pagePathAndXml : pageXmlFiles)
283        {
284            Path pagePath = pagePathAndXml.getLeft();
285            Document propertiesXml = pagePathAndXml.getRight();
286            _importPage(pagePath, propertiesXml);
287        }
288    }
289    
290    private Comparator<Pair<Path, Document>> _pageComparator()
291    {
292        // Sort by path name count, i.e. /foo/bar.xml should be before /foo/bar/baz.xml
293        // Thus, it will ensure parent pages are created when processing a specific XML page file
294        Comparator<Path> firstComparator = Comparator.comparingInt(Path::getNameCount);
295        
296        // Sort by the complete string representation of the parent Path
297        // Just for debug purposes
298        // So as to export /foo/aaa/one then /foo/aaa/two and then /foo/zzz/one
299        // Instead of /foo/aaa/one then /foo/zzz/one and then /foo/aaa/two
300        Comparator<Path> secondComparator = Comparator.comparing(path -> path.getParent().toString());
301        
302        // Sort by order, to keep the order of sibling pages
303        Comparator<Document> thirdComparator = Comparator.comparingInt(this::_getOrder);
304        
305        return Comparator.comparing(Pair<Path, Document>::getLeft, firstComparator)
306                .thenComparing(Comparator.comparing(Pair<Path, Document>::getLeft, secondComparator))
307                .thenComparing(Comparator.comparing(Pair<Path, Document>::getRight, thirdComparator));
308    }
309    
310    private int _getOrder(Document propertiesXml)
311    {
312        try
313        {
314            return Integer.parseInt(Archivers.xpathEvalNonEmpty("page/@order", propertiesXml));
315        }
316        catch (NumberFormatException | AmetysObjectNotImportedException | TransformerException e)
317        {
318            throw new LambdaUtils.LambdaException(e);
319        }
320    }
321    
322    private void _importPage(Path page, Document propertiesXml) throws ImportGlobalFailException
323    {
324        new UnitaryPageImporter(propertiesXml)
325                .unitaryImport(_zipArchivePath, page, _merger, _logger);
326    }
327    
328    private Document _getNullablePagePropertiesXml(Path page)
329    {
330        try
331        {
332            return _getPropertiesXml(page);
333        }
334        catch (Exception e)
335        {
336            _logger.error("An unexpected exception occured when trying to import page for '{}!{}'.", _zipArchivePath, page, e);
337            _report.addError(new ImportError(e));
338            return null;
339        }
340    }
341    
342    private Document _getPropertiesXml(Path pageOrSitemap)
343    {
344        String zipEntryPath = pageOrSitemap.toString();
345        try (InputStream stream = ZipEntryHelper.zipEntryFileInputStream(_zipArchivePath, zipEntryPath))
346        {
347            Document doc = _builder.parse(stream);
348            return doc;
349        }
350        catch (SAXException | IOException e)
351        {
352            throw new RuntimeException(String.format("The exported page/sitemap '%s' could not be read properly.", pageOrSitemap), e);
353        }
354    }
355    
356    private Node _createPage(Path pagePath, String id, Document propertiesXml) throws AmetysObjectNotImportedException, Exception
357    {
358        Path parentPageOrSitemap = pagePath.getParent();
359        String parentRelPath = _relativePath(parentPageOrSitemap);
360        Node parentNode = _retrieveParentPagesContainer(parentRelPath).getNode();
361        String uuid = StringUtils.substringAfter(id, "://");
362        String pageName = MoreFiles.getNameWithoutExtension(pagePath);
363        _logger.info("Creating a Page object for '{}' file (id={}, name={}, parent={})", pagePath, id, pageName, parentNode);
364        
365        Node pageNode = _createChildPage(parentNode, uuid, pageName);
366        ModifiablePage createdPage = _resolvePage(pageNode);
367        _setPageAttributes(createdPage, propertiesXml);
368        _setTags(createdPage, propertiesXml);
369        _setZones(createdPage, propertiesXml);
370        Archivers.unitarySave(pageNode, _logger);
371        
372        ImportReport importAttachmentReport = _setPageAttachments(createdPage, pagePath);
373        _report.addFrom(importAttachmentReport);
374        
375        return pageNode;
376    }
377    
378    private String _relativePath(Path pageOrSitemap)
379    {
380        // for instance, _sitemapFolderPath="/sites/45/7d/www/pages/fr"
381        // relPath=pageOrSitemap.toString()="/sites/45/7d/www/pages/fr/foo/bar"
382        // it should return "foo/bar"
383        
384        // for instance, _sitemapFolderPath="/sites/45/7d/www/pages/fr"
385        // relPath=pageOrSitemap.toString()=""/sites/45/7d/www/pages/fr"
386        // it should return ""
387        
388        // Cannot use _sitemapFolderPath.relativize(pageOrSitemap) as the two ZipPaths do not have the same FileSystem reference :-(
389        // (created with different calls to ZipEntryHelper)
390        String relPath = StringUtils.substringAfter(pageOrSitemap.toString(), _sitemapFolderPath.toString());
391        relPath = StringUtils.strip(relPath, "/");
392        return relPath;
393    }
394    
395    private DefaultTraversableAmetysObject _retrieveParentPagesContainer(String relPath)
396    {
397        return relPath.isEmpty()
398                ? _sitemap
399                : _siteArchiver._defaultPageFactory.getChild(_sitemap, relPath);
400    }
401    
402    private Node _createChildPage(Node parentNode, String uuid, String pageName) throws RepositoryException
403    {
404        // Create a Node with JCR primary type "ametys:defaultPage"
405        // But then call 'replaceNodeWithDesiredUuid' to have it with the desired UUID (srcNode will be removed)
406        Node srcNode = parentNode.addNode(pageName, "ametys:defaultPage");
407        Node nodeWithDesiredUuid = Archivers.replaceNodeWithDesiredUuid(srcNode, uuid);
408        return nodeWithDesiredUuid;
409    }
410    
411    private ModifiablePage _resolvePage(Node node)
412    {
413        return _siteArchiver._defaultPageFactory.getAmetysObject(node, null);
414    }
415    
416    private void _setPageAttributes(ModifiablePage page, Document propertiesXml) throws AmetysObjectNotImportedException, TransformerException, Exception
417    {
418        String title = Archivers.xpathEvalNonEmpty("page/@title", propertiesXml);
419        page.setTitle(title);
420        
421        String longTitle = Archivers.xpathEvalNonEmpty("page/@long-title", propertiesXml);
422        page.setLongTitle(longTitle);
423        
424        PageType type = PageType.valueOf(Archivers.xpathEvalNonEmpty("page/@type", propertiesXml));
425        page.setType(type);
426        
427        switch (type)
428        {
429            case LINK:
430                String url = Archivers.xpathEvalNonEmpty("page/@url", propertiesXml);
431                LinkType urlType = LinkType.valueOf(Archivers.xpathEvalNonEmpty("page/@urlType", propertiesXml));
432                page.setURL(urlType, url);
433                break;
434            case CONTAINER:
435                String template = Archivers.xpathEvalNonEmpty("page/@template", propertiesXml);
436                page.setTemplate(template);
437                break;
438            default:
439                break;
440        }
441        
442        _setAmetysInternalProperties(page, propertiesXml, "page");
443        
444        Element pageAttributesElement = (Element) XPathAPI.selectSingleNode(propertiesXml, "page/attributes");
445        Map<String, Object> values = new ModelLessXMLValuesExtractor(pageAttributesElement, (dataPath, dataType) -> Optional.empty(), _siteArchiver._pageDataTypeExtensionPoint)
446                .extractValues();
447        
448        page.synchronizeValues(values);
449    }
450    
451    private void _setAmetysInternalProperties(PagesContainer pagesContainer, Document propertiesXml, String rootTagName) throws TransformerException, RepositoryException
452    {
453        if (pagesContainer instanceof JCRAmetysObject)
454        {
455            Node pagesContainerNode = ((JCRAmetysObject) pagesContainer).getNode();
456            String xpath = String.format("%s/internal/*", rootTagName);
457            NodeList nodeList = XPathAPI.selectNodeList(propertiesXml, xpath);
458            Function<org.w3c.dom.Node, String> getInternalPropertyName = org.w3c.dom.Node::getNodeName;
459            Function<org.w3c.dom.Node, String> getInternalPropertyValue = org.w3c.dom.Node::getTextContent;
460            
461            Map<String, List<String>> internalPropertiesToSet = IntStream.range(0, nodeList.getLength())
462                    .mapToObj(nodeList::item)
463                    .collect(Collectors.groupingBy(
464                            getInternalPropertyName, 
465                            Collectors.mapping(
466                                getInternalPropertyValue, 
467                                Collectors.toList())));
468            
469            for (Map.Entry<String, List<String>> internalProperty : internalPropertiesToSet.entrySet())
470            {
471                String internalPropertyName = internalProperty.getKey();
472                List<String> internalPropertyValue = internalProperty.getValue();
473                _logger.debug("Adding to '{}' internal property '{}' with value '{}'", pagesContainer, internalPropertyName, internalPropertyValue);
474                pagesContainerNode.setProperty(internalPropertyName, internalPropertyValue.toArray(String[]::new));
475            }
476        }
477    }
478    
479    private void _setTags(ModifiablePage page, Document propertiesXml) throws TransformerException
480    {
481        NodeList nodeList = XPathAPI.selectNodeList(propertiesXml, "page/tags/*");
482        String[] tags = IntStream.range(0, nodeList.getLength())
483                .mapToObj(nodeList::item)
484                .map(org.w3c.dom.Node::getNodeName)
485                .toArray(String[]::new);
486        for (String tag : tags)
487        {
488            _logger.debug("Tagging page '{}' with tag '{}'", page, tag);
489            page.tag(tag);
490        }
491    }
492    
493    private ImportReport _setPageAttachments(ModifiablePage page, Path pagePath) throws IOException, RepositoryException
494    {
495        if (page instanceof DefaultPage)
496        {
497            Node pageNode = ((DefaultPage) page).getNode();
498            
499            // in case ametys-internal:attachments is already created
500            if (pageNode.hasNode(DefaultPage.ATTACHMENTS_NODE_NAME))
501            {
502                pageNode.getNode(DefaultPage.ATTACHMENTS_NODE_NAME).remove();
503            }
504            
505            String commonPrefix = new StringBuilder()
506                    .append(ArchiveHandler.METADATA_PREFIX)
507                    .append(StringUtils.strip(pagePath.getParent().toString(), "/"))
508                    .append("/")
509                    .append(MoreFiles.getNameWithoutExtension(pagePath))
510                    .append("/")
511                    .append("_attachments")
512                    .append("/")
513                    .toString();
514            return _siteArchiver._resourcesArchiverHelper.importCollection(commonPrefix, pageNode, _zipArchivePath, _merger);
515        }
516        
517        return new ImportReport();
518    }
519    
520    private void _setZones(ModifiablePage page, Document propertiesXml) throws TransformerException, AmetysObjectNotImportedException, Exception
521    {
522        NodeList zoneNodeList = XPathAPI.selectNodeList(propertiesXml, "page/pageContents/zone");
523        for (int i = 0; i < zoneNodeList.getLength(); i++)
524        {
525            org.w3c.dom.Node zoneNode = zoneNodeList.item(i);
526            _setZone(page, zoneNode);
527        }
528    }
529    
530    private void _setZone(ModifiablePage page, org.w3c.dom.Node zoneNode) throws TransformerException, AmetysObjectNotImportedException, Exception
531    {
532        String zoneName = Archivers.xpathEvalNonEmpty("@name", zoneNode);
533        ModifiableZone zone = page.createZone(zoneName);
534        
535        Element zoneAttributesElement = (Element) XPathAPI.selectSingleNode(zoneNode, "attributes");
536        Map<String, Object> values = new ModelLessXMLValuesExtractor(zoneAttributesElement, (dataPath, dataType) -> Optional.empty(), _siteArchiver._pageDataTypeExtensionPoint)
537                .extractValues();
538        zone.synchronizeValues(values);
539        
540        NodeList zoneItemNodeList = XPathAPI.selectNodeList(zoneNode, "zoneItem");
541        for (int i = 0; i < zoneItemNodeList.getLength(); i++)
542        {
543            org.w3c.dom.Node zoneItemNode = zoneItemNodeList.item(i);
544            _setZoneItem(zone, zoneItemNode);
545        }
546    }
547    
548    private void _setZoneItem(ModifiableZone zone, org.w3c.dom.Node zoneItemNode) throws TransformerException, Exception
549    {
550        ModifiableZoneItem zoneItem = zone.addZoneItem();
551        ZoneType zoneType = ZoneType.valueOf(Archivers.xpathEvalNonEmpty("@type", zoneItemNode));
552        zoneItem.setType(zoneType);
553        _setZoneItemAttributes(zoneItem, zoneItemNode);
554        
555        switch (zoneType)
556        {
557            case CONTENT:
558                _setZoneItemContent(zoneItem, zoneItemNode);
559                break;
560            case SERVICE:
561                String serviceId = Archivers.xpathEvalNonEmpty("@serviceId", zoneItemNode);
562                zoneItem.setServiceId(serviceId);
563                _setZoneItemServiceParams(zoneItem, zoneItemNode, serviceId);
564                break;
565            default:
566                break;
567        }
568    }
569    
570    private void _setZoneItemAttributes(ModifiableZoneItem zoneItem, org.w3c.dom.Node zoneItemNode) throws TransformerException, Exception
571    {
572        Element zoneItemAttributesElement = (Element) XPathAPI.selectSingleNode(zoneItemNode, "attributes");
573        Map<String, Object> values = new ModelLessXMLValuesExtractor(zoneItemAttributesElement, (dataPath, type) -> Optional.empty(), _siteArchiver._pageDataTypeExtensionPoint)
574                .extractValues();
575        zoneItem.synchronizeValues(values);
576    }
577    
578    private void _setZoneItemContent(ModifiableZoneItem zoneItem, org.w3c.dom.Node zoneItemNode) throws TransformerException, AmetysObjectNotImportedException
579    {
580        String contentId = Archivers.xpathEvalNonEmpty("@contentId", zoneItemNode);
581        if (_siteArchiver._resolver.hasAmetysObjectForId(contentId))
582        {
583            Content content = _siteArchiver._resolver.resolveById(contentId);
584            zoneItem.setContent(content);
585        }
586        XObject viewNameXObject = XPathAPI.eval(zoneItemNode, "@viewName");
587        if (viewNameXObject.getType() != XObject.CLASS_NULL)
588        {
589            String viewName = viewNameXObject.str();
590            zoneItem.setViewName(viewName);
591        }
592    }
593    
594    private void _setZoneItemServiceParams(ModifiableZoneItem zoneItem, org.w3c.dom.Node zoneItemNode, String serviceId) throws Exception
595    {
596        Element serviceParamsElement = (Element) XPathAPI.selectSingleNode(zoneItemNode, "serviceParameters");
597        Service service = _getService(serviceId);
598        if (service == null)
599        {
600            String message = String.format("Service with id '%s' for ZoneItem \"%s\" does not exist", serviceId, zoneItem);
601            _logger.error(message);
602            _report.addError(new ImportError(new Exception(message)));
603        }
604        else
605        {
606            XMLValuesExtractorAdditionalDataGetter additionalDataGetter = (dataPath, type) -> Optional.empty();
607            View view = View.of(service);
608            Map<String, Object> values = new ModelAwareXMLValuesExtractor(serviceParamsElement, additionalDataGetter, service)
609                    .extractValues(view);
610            
611            ModifiableModelAwareDataHolder serviceParametersDataHolder = zoneItem.getServiceParameters();
612            serviceParametersDataHolder.synchronizeValues(view, values);
613        }
614    }
615    
616    private Service _getService(String serviceId)
617    {
618        return _siteArchiver._serviceExtensionPoint.getExtension(serviceId);
619    }
620}