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.contentio.archive;
017
018import java.io.IOException;
019import java.nio.file.Path;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.Optional;
023import java.util.stream.Stream;
024import java.util.zip.ZipOutputStream;
025
026import javax.jcr.Node;
027import javax.jcr.RepositoryException;
028import javax.jcr.Session;
029
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.avalon.framework.service.Serviceable;
033
034import org.ametys.plugins.explorer.resources.jcr.JCRResourcesCollection;
035import org.ametys.plugins.repository.AmetysObjectResolver;
036import org.ametys.plugins.repository.UnknownAmetysObjectException;
037import org.ametys.plugins.repository.jcr.JCRAmetysObject;
038import org.ametys.runtime.i18n.I18nizableText;
039import org.ametys.runtime.plugin.component.AbstractLogEnabled;
040
041/**
042 * Archives resources in /ametys:root/ametys:resources
043 */
044public class ResourcesArchiver extends AbstractLogEnabled implements Archiver, Serviceable
045{
046    /** Archiver id. */
047    public static final String ID = "resources";
048    
049    private AmetysObjectResolver _resolver;
050    private ResourcesArchiverHelper _resourcesArchiverHelper;
051    
052    private ManifestReaderWriter _manifestReaderWriter = new ResourcesArchiverManifestReaderWriter();
053    
054    @Override
055    public void service(ServiceManager manager) throws ServiceException
056    {
057        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
058        _resourcesArchiverHelper = (ResourcesArchiverHelper) manager.lookup(ResourcesArchiverHelper.ROLE);
059    }
060    
061    @Override
062    public ManifestReaderWriter getManifestReaderWriter()
063    {
064        return _manifestReaderWriter;
065    }
066    
067    private class ResourcesArchiverManifestReaderWriter implements ManifestReaderWriter
068    {
069        private static final String __DATA = "resources";
070        
071        @Override
072        public Object getData()
073        {
074            return __DATA;
075        }
076        
077        @SuppressWarnings("synthetic-access")
078        @Override
079        public Stream<PartialImport> toPartialImports(Object data)
080        {
081            return Optional.ofNullable(data)
082                    .filter(String.class::isInstance)
083                    .map(String.class::cast)
084                    .filter(__DATA::equals)
085                    .map(Stream::of)
086                    .orElseGet(() ->
087                    {
088                        getLogger().warn("Unexpected manifest data '{}', we would expect '{}', the ZIP archive probably comes from a different version of Ametys.", data, __DATA);
089                        return Stream.empty();
090                    })
091                    .map(resourcesStr -> PartialImport.of(resourcesStr, new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_ARCHIVE_IMPORT_RESOURCE_ARCHIVER_OPTION_ALL")));
092        }
093    }
094    
095    @Override
096    public void export(ZipOutputStream zos) throws IOException
097    {
098        Optional<JCRResourcesCollection> rootResources = _rootResources();
099        if (rootResources.isPresent())
100        {
101            _resourcesArchiverHelper.exportCollection(rootResources.get(), zos, "resources/");
102        }
103    }
104    
105    private Optional<JCRResourcesCollection> _rootResources()
106    {
107        try
108        {
109            return Optional.of(_resolver.resolveByPath("/ametys:resources"));
110        }
111        catch (UnknownAmetysObjectException e)
112        {
113            return Optional.empty();
114        }
115    }
116    
117    private void _deleteBeforePartialImport(JCRResourcesCollection rootResources) throws IOException
118    {
119        try
120        {
121            Node allResourcesNode = rootResources.getNode();
122            Session session = allResourcesNode.getParent().getSession();
123            allResourcesNode.remove();
124            session.save();
125        }
126        catch (RepositoryException e)
127        {
128            throw new IOException(e);
129        }
130    }
131    
132    @Override
133    public Collection<String> managedPartialImports(Collection<String> partialImports)
134    {
135        return partialImports.contains(ID)
136                ? Collections.singletonList(ID)
137                : Collections.EMPTY_LIST;
138    }
139    
140    @Override
141    public ImportReport partialImport(Path zipPath, Collection<String> partialImports, Merger merger, boolean deleteBefore) throws IOException
142    {
143        Optional<JCRResourcesCollection> rootResources = _rootResources();
144        if (rootResources.isPresent())
145        {
146            // ALWAYS remove root resources, even if deleteBefore is false because it MUST be re-created with the good UUID
147            _deleteBeforePartialImport(rootResources.get());
148        }
149        
150        JCRAmetysObject root = _resolver.resolveByPath("/");
151        Node parentOfRootResources = root.getNode();
152        return _resourcesArchiverHelper.importCollection("resources/", parentOfRootResources, zipPath, merger);
153    }
154}