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.DirectoryStream;
020import java.nio.file.Files;
021import java.nio.file.Path;
022import java.util.ArrayList;
023import java.util.List;
024import java.util.Optional;
025import java.util.zip.ZipOutputStream;
026
027import javax.jcr.Node;
028import javax.jcr.NodeIterator;
029import javax.jcr.RepositoryException;
030
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.xml.sax.ContentHandler;
034
035import org.ametys.plugins.contentio.archive.DefaultPluginArchiver;
036import org.ametys.plugins.contentio.archive.ImportReport;
037import org.ametys.plugins.contentio.archive.Merger;
038import org.ametys.plugins.contentio.archive.ResourcesArchiverHelper;
039import org.ametys.plugins.contentio.archive.SystemViewHandler;
040import org.ametys.plugins.contentio.archive.ZipEntryHelper;
041import org.ametys.plugins.explorer.resources.ResourceCollection;
042import org.ametys.plugins.repository.AmetysObjectResolver;
043
044/**
045 * {@link SitePluginArchiver} handling the "web-explorer" part, holding all shared resources.
046 */
047public class WebExplorerArchiver extends DefaultPluginArchiver
048{
049    private static final String __WEB_EXPLORER_NODE_NAME = "web-explorer";
050    private static final String __SHARED_RESOURCES_NODE_NAME = "shared-resources";
051    
052    private AmetysObjectResolver _resolver;
053    private ResourcesArchiverHelper _resourcesArchiverHelper;
054    
055    @Override
056    public void service(ServiceManager manager) throws ServiceException
057    {
058        super.service(manager);
059        
060        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
061        _resourcesArchiverHelper = (ResourcesArchiverHelper) manager.lookup(ResourcesArchiverHelper.ROLE);
062    }
063    
064    @Override
065    public void export(String pluginName, Node node, ZipOutputStream zos, String prefix) throws IOException
066    {
067        super.export(pluginName, node, zos, prefix);
068        
069        try
070        {
071            if (node.hasNode(__SHARED_RESOURCES_NODE_NAME))
072            {
073                Node sharedResourcesNode = node.getNode(__SHARED_RESOURCES_NODE_NAME);
074                NodeIterator it = sharedResourcesNode.getNodes();
075                while (it.hasNext())
076                {
077                    Node resources = it.nextNode();
078                    ResourceCollection rootResources = _resolver.resolve(resources, false);
079                    _resourcesArchiverHelper.exportCollection(rootResources, zos, prefix + "/shared-resources/" + resources.getName() + "/");
080                }
081            }
082        }
083        catch (RepositoryException e)
084        {
085            throw new RuntimeException("Unable to archive plugin " + pluginName, e);
086        }
087    }
088    
089    @Override
090    protected ContentHandler getSystemViewHandler(ContentHandler initialHandler)
091    {
092        return new SystemViewHandler(initialHandler, name -> List.of("ametys:contents", "shared-resources").contains(name), __ -> false);
093    }
094    
095    @Override
096    public ImportReport partialImport(String pluginName, Node allPluginsNode, Path zipPath, String zipPluginEntryPath, Merger merger) throws IOException
097    {
098        // ALWAYS remove web-explorer node, even if merger.deleteBeforePartialImport() is false because it MUST be re-created with the good UUID with the super method (JCR import)
099        _removeWebExplorerNode(allPluginsNode);
100        ImportReport importPluginReport = super.partialImport(pluginName, allPluginsNode, zipPath, zipPluginEntryPath, merger);
101        
102        Node webExplorerNode = _getWebExplorerNode(allPluginsNode);
103        
104        ImportReport importResourceReport = new ImportReport();
105        String zipSharedResourcesEntryPath = zipPluginEntryPath + "/" + "shared-resources/";
106        if (ZipEntryHelper.zipEntryFolderExists(zipPath, zipSharedResourcesEntryPath))
107        {
108            importResourceReport = _importSharedResources(webExplorerNode, zipPath, zipSharedResourcesEntryPath, merger);
109        }
110        
111        return ImportReport.union(importPluginReport, importResourceReport);
112    }
113    
114    private void _removeWebExplorerNode(Node allPluginsNode) throws IOException
115    {
116        try
117        {
118            if (allPluginsNode.hasNode(__WEB_EXPLORER_NODE_NAME))
119            {
120                allPluginsNode.getNode(__WEB_EXPLORER_NODE_NAME).remove();
121                allPluginsNode.getSession().save();
122            }
123        }
124        catch (RepositoryException e)
125        {
126            throw new IOException(e);
127        }
128    }
129    
130    private Node _getWebExplorerNode(Node allPluginsNode) throws IOException
131    {
132        try
133        {
134            Node webExplorerNode = allPluginsNode.getNode(__WEB_EXPLORER_NODE_NAME);
135            return webExplorerNode;
136        }
137        catch (RepositoryException e)
138        {
139            throw new IOException("Unexpected state. The '" + __WEB_EXPLORER_NODE_NAME + "' node was not imported.", e);
140        }
141    }
142    
143    private ImportReport _importSharedResources(Node webExplorerNode, Path zipPath, String zipSharedResourcesEntryPath, Merger merger) throws IOException
144    {
145        List<ImportReport> reports = new ArrayList<>();
146        
147        Node sharedResourcesNode;
148        try
149        {
150            sharedResourcesNode = webExplorerNode.hasNode(__SHARED_RESOURCES_NODE_NAME)
151                    ? webExplorerNode.getNode(__SHARED_RESOURCES_NODE_NAME)
152                    : webExplorerNode.addNode(__SHARED_RESOURCES_NODE_NAME, "ametys:unstructured");
153        }
154        catch (RepositoryException e)
155        {
156            throw new IOException(e);
157        }
158        
159        DirectoryStream<Path> roots = ZipEntryHelper.children(
160                zipPath, 
161                Optional.of(zipSharedResourcesEntryPath), 
162                Files::isDirectory);
163        try (roots)
164        {
165            for (Path rootPath : roots)
166            {
167                Node parentOfRootResources = sharedResourcesNode;
168                ImportReport importCollectionReport = _resourcesArchiverHelper.importCollection(rootPath.toString() + "/", parentOfRootResources, zipPath, merger);
169                reports.add(importCollectionReport);
170            }
171        }
172        
173        return ImportReport.union(reports);
174    }
175}