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