001/*
002*  Copyright 2016 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.workspaces.repository.maintenance;
017
018import java.util.ArrayList;
019import java.util.List;
020import java.util.Map;
021
022import javax.jcr.RepositoryException;
023
024import org.apache.commons.lang.StringUtils;
025import org.apache.commons.lang3.tuple.Pair;
026import org.apache.jackrabbit.core.RepositoryContext;
027import org.apache.jackrabbit.core.RepositoryImpl;
028import org.apache.jackrabbit.core.config.RepositoryConfig;
029import org.apache.jackrabbit.core.persistence.IterablePersistenceManager;
030import org.apache.jackrabbit.core.persistence.PersistenceManager;
031import org.apache.jackrabbit.core.version.InternalVersionManagerImpl;
032import org.slf4j.Logger;
033
034import org.ametys.core.schedule.progression.ContainerProgressionTracker;
035import org.ametys.core.schedule.progression.ProgressionTracker;
036import org.ametys.core.util.DateUtils;
037import org.ametys.runtime.plugin.component.LogEnabled;
038
039/**
040 * Jackrabbit maintenance tasks implementations should extends this class.
041 */
042public abstract class AbstractMaintenanceTask implements LogEnabled
043{
044    /** ContainerProgressionTracker */
045    protected ContainerProgressionTracker _progress;
046
047    /** Logger for traces */
048    protected Logger _logger;
049    
050    private boolean _isFinished;
051
052    /** The repository config
053     * Must be null when _repositoryContext is not null
054     */
055    private RepositoryConfig _repositoryConfig;
056    
057    /** The repository context
058     * Must be null when _repositoryConfig is not null
059     */
060    private RepositoryContext _repositoryContext;
061    
062    /**
063     * Indicate if the task need to be executed with an offline repository
064     * @return true if the repository need to be offline
065     */
066    public boolean requiresOffline()
067    {
068        return true;
069    }
070    
071    /**
072     * Execute the task
073     * @param repositoryInfo a pair providing either the repository config (left) or the repository context (right). Only one will be valued. The other one is null.
074     * @throws RepositoryException If an error with the repository occurs while the task is executed.
075     */
076    public void execute(Pair<RepositoryConfig, RepositoryContext> repositoryInfo) throws RepositoryException 
077    {
078        _repositoryConfig = repositoryInfo.getLeft();
079        _repositoryContext = repositoryInfo.getRight();
080
081        long startTime = System.currentTimeMillis();
082        
083        setLogger();
084
085        _logger.info("Executing task.");
086
087        try
088        {
089            // task specific initialization.
090            initialize();
091
092            // Perform the task.
093            apply();
094
095            // Mark the task as finished.
096            setFinished();
097        }
098        catch (Exception e)
099        {
100            setInErrorState(e);
101            throw e;
102        }
103        finally
104        {
105            close();
106
107            _logger.info("End of the task.");
108
109            long elapsedTime = System.currentTimeMillis() - startTime;
110            _logger.info("Done in " + _getFormattedDuration(elapsedTime));
111        }
112    }
113
114    /**
115     * Initialize the tasks.
116     * This method can also create the {@link ProgressionTracker} object bounded to the task.
117     * @throws RepositoryException If a repository exception
118     */
119    protected void initialize() throws RepositoryException
120    {
121        return;
122    }
123
124    /**
125     * Apply the tasks (within the execute method()).
126     * @throws RepositoryException If a repository exception
127     */
128    protected abstract void apply() throws RepositoryException;
129
130    /**
131     * Close the tasks
132     */
133    protected void close()
134    {
135        if (_repositoryConfig != null)
136        {
137            // We are in safe mode. So we need to shutdown the repository if we created it
138            if (_repositoryContext != null && _repositoryContext.getRepository() != null)
139            {
140                _logger.info("Shutting down the repository created for maintenance operation");
141                _repositoryContext.getRepository().shutdown();
142            }
143        }
144        return;
145    }
146    
147    /**
148     * Set the tasks logger.
149     */
150    protected abstract void setLogger();
151    
152    @Override
153    public void setLogger(Logger logger)
154    {
155        _logger = logger;
156    }
157
158    /**
159     * Get progress information.
160     * @return a map containing information on the progress status.
161     */
162    public Map<String, Object> getProgressInfo() 
163    {
164        if (_progress != null)
165        {
166            return _progress.toJSON();
167        }
168        else
169        {
170            return null;
171        }
172    }
173
174    /**
175     * Is the execution of the task finished?
176     * @return true if finished
177     */
178    public boolean isFinished()
179    {
180        return _isFinished;
181    }
182
183    private synchronized void setFinished()
184    {
185        _isFinished = true;
186    }
187
188    private synchronized void setInErrorState(Exception e)
189    {
190        _logger.error(e.getLocalizedMessage(), e);
191
192        _isFinished = true;
193    }
194
195    /**
196     * Transforms millisecond to a formatted string representing a duration.
197     * @param elapsedTime milleseconds corresponding to the duration.
198     * @return Pretty formatted string.
199     */
200    protected String _getFormattedDuration(long elapsedTime)
201    {
202        String formatted = DateUtils.formatDuration(elapsedTime);
203        return StringUtils.defaultIfEmpty(formatted, "0s");
204    }
205    
206    /**
207     * Get the repository config
208     * @return the repository config
209     */
210    protected RepositoryConfig getRepositoryConfig()
211    {
212        if (_repositoryConfig != null)
213        {
214            return _repositoryConfig;
215        }
216        else
217        {
218            return _repositoryContext.getRepositoryConfig();
219        }
220    }
221    
222    /**
223     * Create a repository context.
224     * If a repository context was provided, or a repository was already created, then an error will be thrown
225     * @return the repository context
226     * @throws RepositoryException if an error occurred
227     */
228    protected RepositoryContext createRepositoryContext() throws RepositoryException
229    {
230        if (_repositoryConfig != null && _repositoryContext == null)
231        {
232            _logger.info("Creating repository for maintenance operation");
233            _repositoryContext = RepositoryContext.create(_repositoryConfig);
234            return _repositoryContext;
235        }
236        else
237        {
238            _logger.error("Trying to instanciate a new repository when a repository is already existing");
239            throw new IllegalStateException("Trying to instanciate a new repository when a repository is already existing");
240        }
241    }
242    
243    /**
244     * Get the current repository context if one was provided or already created. Or instantiate a new one from the provided config.
245     * @return a repository context
246     * @throws RepositoryException when an error occurred
247     */
248    protected RepositoryContext getOrCreateRepositoryContext() throws RepositoryException
249    {
250        if (_repositoryContext == null)
251        {
252            return createRepositoryContext();
253        }
254        else
255        {
256            return _repositoryContext;
257        }
258    }
259    
260    
261    /**
262     * Get the repository.
263     * The repository can either be retrieved from a provided {@link RepositoryContext} or from a context created by {@link #createRepositoryContext()}, {@link #createRepository()} or {@link #getOrCreateRepository()}.
264     * @return the repository
265     */
266    protected RepositoryImpl getRepository()
267    {
268        return _repositoryContext.getRepository();
269    }
270    
271    /**
272     * Create a repository. If there is already an existing repository or repository context, a error will be thrown
273     * @return a repository
274     * @throws RepositoryException when an error occurred
275     */
276    protected RepositoryImpl createRepository() throws RepositoryException
277    {
278        return createRepositoryContext().getRepository();
279    }
280    
281    /**
282     * Get the repository from a provided context. Or instantiate a new repository from the provided config
283     * @return a repository
284     * @throws RepositoryException when an error occurred
285     */
286    protected RepositoryImpl getOrCreateRepository() throws RepositoryException
287    {
288        return getOrCreateRepositoryContext().getRepository();
289    }
290    
291    /**
292     * Get the persistence manager from all workspace and the version manager from the repository context
293     * If any of those persistence manager is not an {@link IterablePersistenceManager} null will be returned
294     * @param context the repository context
295     * @return the persistence managers
296     * @throws RepositoryException if an error occurred
297     */
298    protected static List<IterablePersistenceManager> getAllPersistenceManager(RepositoryContext context) throws RepositoryException
299    {
300        // Workaround to get the list of the PersistenceManager
301        List<IterablePersistenceManager> pmList = new ArrayList<>();
302    
303        // PM of version manager
304        InternalVersionManagerImpl vm = context.getInternalVersionManager();
305        if (vm.getPersistenceManager() instanceof IterablePersistenceManager versionPersistenceManager)
306        {
307            // For some reason, previous implementation return null if any PM was not iterable.
308            // Kept the implementation the same.
309            pmList.add(versionPersistenceManager);
310        }
311        else
312        {
313            return null;
314        }
315    
316        // PMs of workspaces.
317        String[] wspNames = context.getWorkspaceManager().getWorkspaceNames();
318        for (String wspName : wspNames)
319        {
320            final PersistenceManager persistenceManager = context.getWorkspaceInfo(wspName).getPersistenceManager();
321            if (persistenceManager instanceof IterablePersistenceManager iterablePersistenceManager)
322            {
323                pmList.add(iterablePersistenceManager);
324            }
325            else
326            {
327                // For some reason, previous implementation return null if any PM was not iterable.
328                // Kept the implementation the same.
329                return null;
330            }
331        }
332        return pmList;
333    }
334
335}