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.contentio.archive;
017
018import java.io.IOException;
019import java.io.InputStream;
020
021import javax.jcr.ImportUUIDBehavior;
022import javax.jcr.RepositoryException;
023import javax.jcr.Session;
024
025import org.apache.avalon.framework.activity.Disposable;
026import org.apache.avalon.framework.activity.Initializable;
027import org.apache.avalon.framework.component.Component;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.commons.lang.NotImplementedException;
032import org.apache.excalibur.source.SourceResolver;
033import org.slf4j.Logger;
034
035import org.ametys.plugins.repository.AmetysObject;
036import org.ametys.plugins.repository.AmetysObjectResolver;
037import org.ametys.plugins.repository.RemovableAmetysObject;
038import org.ametys.runtime.plugin.component.AbstractLogEnabled;
039
040/**
041 * The component holding the {@link Merger} implementations
042 */
043public class Mergers extends AbstractLogEnabled implements Component, Serviceable, Initializable, Disposable
044{
045    /** Avalon role. */
046    public static final String ROLE = Mergers.class.getName();
047    
048    static final Merger DELETE_BEFORE = new DeleteBefore();
049    static final Merger IGNORE = new Ignore();
050    static final Merger REPLACE = new Replace();
051    static final Merger FAIL = new Fail();
052    
053    private static final Merger[] __IMPLEMENTATIONS = new Merger[] {DELETE_BEFORE, IGNORE, REPLACE, FAIL};
054    
055    AmetysObjectResolver _ametysObjectResolver;
056    SourceResolver _sourceResolver;
057    
058    @Override
059    public void service(ServiceManager manager) throws ServiceException
060    {
061        _ametysObjectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
062        _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
063    }
064    
065    private static void _bindMergersComponent(Mergers mergersComponent)
066    {
067        for (Merger merger : __IMPLEMENTATIONS)
068        {
069            if (merger instanceof AbstractMerger)
070            {
071                ((AbstractMerger) merger).lateBindDependencies(mergersComponent);
072            }
073        }
074    }
075    
076    @Override
077    public void initialize() throws Exception
078    {
079        _bindMergersComponent(this);
080    }
081    
082    @Override
083    public void dispose()
084    {
085        _bindMergersComponent(null);
086    }
087    
088    private abstract static class AbstractMerger implements Merger
089    {
090        /** The {@link Mergers} component to access to other components/services/dependencies */
091        protected Mergers _mergers;
092
093        void lateBindDependencies(Mergers mergersComponent)
094        {
095            _mergers = mergersComponent;
096        }
097    }
098    
099    // Override just to avoid warnings on usage of _mergers.getLogger()
100    @Override
101    protected Logger getLogger()
102    {
103        return super.getLogger();
104    }
105    
106    private static class DeleteBefore implements Merger
107    {
108        @Override
109        public boolean deleteBeforePartialImport()
110        {
111            return true;
112        }
113        
114        @Override
115        public boolean needsMerge(String id)
116        {
117            return false;
118        }
119
120        @Override
121        public AfterMerge merge(String id)
122        {
123            throw new NotImplementedException("Should never be called");
124        }
125        
126        @Override
127        public int getImportUuidBehavior()
128        {
129            return ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW; // should never have a collision, so choose here to throw
130        }
131    }
132    
133    private static class Ignore extends AbstractMerger
134    {
135        @Override
136        public boolean needsMerge(String id)
137        {
138            return _mergers._ametysObjectResolver.hasAmetysObjectForId(id);
139        }
140
141        @Override
142        public AfterMerge merge(String id)
143        {
144            // Nothing to do
145            // Indeed, we want to ignore the import of the object
146            // The already existing object is kept
147            // We return STOP_PROCESS so as to not import the object from the ZIP archive
148            _mergers.getLogger().debug("Object for id '{}' will be ignored as it already exists ('Ignore' MergePolicy)...", id);
149            return AfterMerge.STOP_PROCESS;
150        }
151        
152        @Override
153        public int getImportUuidBehavior()
154        {
155            // There is no behavior for that
156            // So we return the safest option here, i.e. throw an error as soon as possible
157            return ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW;
158        }
159        
160        @Override
161        public void jcrImportXml(Session session, String parentAbsPath, InputStream in) throws RepositoryException, IOException
162        {
163            IgnoreMergerHelper.jcrImportXml(session, parentAbsPath, in, getImportUuidBehavior(), _mergers);
164        }
165    }
166    
167    private static class Replace extends AbstractMerger
168    {
169        @Override
170        public boolean needsMerge(String id)
171        {
172            return _mergers._ametysObjectResolver.hasAmetysObjectForId(id);
173        }
174
175        @Override
176        public AfterMerge merge(String id)
177        {
178            // We remove the existing object
179            // Indeed, we want to replace this version by the one from the ZIP archive
180            // We return CONTINUE_PROCESS so as to import the object
181            AmetysObject ametysObject = _mergers._ametysObjectResolver.resolveById(id);
182            if (ametysObject instanceof RemovableAmetysObject)
183            {
184                _mergers.getLogger().debug("Removing existing object '{}' as it will be re-created ('Replace' MergePolicy)...", ametysObject);
185                ((RemovableAmetysObject) ametysObject).remove();
186                return AfterMerge.CONTINUE_PROCESS;
187            }
188            else
189            {
190                throw new IllegalArgumentException(String.format(
191                        "The given object (%s) for id '%s' cannot be merged with a 'Replace' MergePolicy, as the existing object is not a RemovableAmetysObject",
192                        ametysObject,
193                        id));
194            }
195        }
196        
197        @Override
198        public int getImportUuidBehavior()
199        {
200            return ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING;
201        }
202    }
203    
204    private static class Fail extends AbstractMerger
205    {
206        @Override
207        public boolean needsMerge(String id)
208        {
209            return _mergers._ametysObjectResolver.hasAmetysObjectForId(id);
210        }
211
212        @Override
213        public AfterMerge merge(String id) throws MergeException
214        {
215            AmetysObject ametysObject = _mergers._ametysObjectResolver.resolveById(id);
216            throw new MergeException(String.format(
217                    "The import failed because the object with id '%s' already exists and the 'Fail' MergePolicy was selected.", 
218                    ametysObject,
219                    id));
220        }
221        
222        @Override
223        public int getImportUuidBehavior()
224        {
225            return ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW;
226        }
227    }
228}