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}