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}