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.io.File; 019import java.util.Map; 020import java.util.concurrent.Callable; 021import java.util.concurrent.ExecutorService; 022import java.util.concurrent.Executors; 023import java.util.concurrent.FutureTask; 024 025import javax.jcr.RepositoryException; 026import javax.jcr.UnsupportedRepositoryOperationException; 027import javax.servlet.ServletException; 028 029import org.apache.avalon.framework.component.Component; 030import org.apache.avalon.framework.context.ContextException; 031import org.apache.avalon.framework.context.Contextualizable; 032import org.apache.avalon.framework.logger.AbstractLogEnabled; 033import org.apache.avalon.framework.logger.Logger; 034import org.apache.avalon.framework.service.ServiceException; 035import org.apache.avalon.framework.service.ServiceManager; 036import org.apache.avalon.framework.service.Serviceable; 037import org.apache.cocoon.Constants; 038import org.apache.cocoon.environment.Context; 039import org.apache.commons.lang3.tuple.Pair; 040import org.apache.jackrabbit.core.RepositoryContext; 041import org.apache.jackrabbit.core.config.ConfigurationException; 042import org.apache.jackrabbit.core.config.RepositoryConfig; 043 044import org.ametys.core.util.SystemStatus; 045import org.ametys.plugins.repositoryapp.RepositoryProvider; 046import org.ametys.runtime.util.AmetysHomeHelper; 047 048/** 049 * The MaintenanceTaskManager Component 050 */ 051public class MaintenanceTaskManager extends AbstractLogEnabled implements Component, Serviceable, Contextualizable 052{ 053 /** The avalon role. */ 054 public static final String ROLE = MaintenanceTaskManager.class.getName(); 055 056 /** The repository unavailable system status */ 057 public static final String REPOSITORY_UNAVAILABLE = "REPOSITORY_UNAVAILABLE"; 058 059 /** The maintenance task running system status */ 060 public static final String MAINTENANCE_TASK_RUNNING = "MAINTENANCE_TASK_RUNNING"; 061 062 /** The maintenance task ended system status */ 063 public static final String MAINTENANCE_TASK_ENDED = "MAINTENANCE_TASK_ENDED"; 064 065 /** The maintenance running task */ 066 protected static AbstractMaintenanceTask _runningTask; 067 068 /** The maintenance running task */ 069 protected static MaintenanceTaskType _lastRunningTaskType; 070 071 /** The maintenance running task type */ 072 protected MaintenanceTaskType _runningTaskType; 073 074 /** System status provider */ 075 protected SystemStatus _systemStatus; 076 077 /** The repository provider. */ 078 protected RepositoryProvider _repositoryProvider; 079 080 private final ExecutorService _executor = Executors.newFixedThreadPool(1); 081 082 private FutureTask<Void> _future; 083 084 private boolean _repositoryShutdown; 085 086 private Context _context; 087 088 /** Task types */ 089 public enum MaintenanceTaskType 090 { 091 /** Remove unused history */ 092 REMOVE_UNUSED_HISTORY, 093 /** data store GC */ 094 DATA_STORE_GARBAGE_COLLECTOR, 095 /** reindexing task */ 096 REINDEXING, 097 /** consistency check */ 098 CONSISTENCY_CHECK, 099 /** remove inconsistent references */ 100 REMOVE_INCOHERENT_REFERENCES, 101 /** reclaim unused space in the derby data store */ 102 RECLAIM_UNUSED_SPACE; 103 } 104 105 @Override 106 public void contextualize(org.apache.avalon.framework.context.Context context) throws ContextException 107 { 108 _context = (Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 109 } 110 111 @Override 112 public void service(ServiceManager manager) throws ServiceException 113 { 114 if (manager.hasService(RepositoryProvider.ROLE)) 115 { 116 // In safe-mode, the repository provider can not be retrieved 117 _repositoryProvider = (RepositoryProvider) manager.lookup(RepositoryProvider.ROLE); 118 } 119 _systemStatus = (SystemStatus) manager.lookup(SystemStatus.ROLE); 120 } 121 122 /** 123 * Launch a maintenance task 124 * @param type The maintenance task type 125 * @return true if a task has been launched 126 * @throws Exception when an error occurred 127 */ 128 public boolean launch(MaintenanceTaskType type) throws Exception 129 { 130 boolean launched = false; 131 if (!isTaskRunning()) 132 { 133 try 134 { 135 run(type); 136 launched = true; 137 } 138 catch (ConfigurationException e) 139 { 140 getLogger().error("Unable to get the configuration of the repository.", e); 141 throw e; 142 } 143 catch (RepositoryException e) 144 { 145 getLogger().error("Unable to launch the task.", e); 146 throw e; 147 } 148 } 149 return launched; 150 } 151 152 private void run(final MaintenanceTaskType type) throws ConfigurationException, RepositoryException 153 { 154 _runningTaskType = type; 155 _runningTask = _createTask(type); 156 157 // add system status 158 if (_runningTask.requiresOffline()) 159 { 160 _systemStatus.addStatus(REPOSITORY_UNAVAILABLE); 161 } 162 _systemStatus.addStatus(MAINTENANCE_TASK_RUNNING); 163 _systemStatus.removeStatus(MAINTENANCE_TASK_ENDED); 164 165 final Logger logger = getLogger(); 166 167 168 Pair<RepositoryConfig, RepositoryContext> repositoryInfo = _getRepositoryInfo(); 169 170 _future = new FutureTask<>(new Callable<Void>() 171 { 172 public Void call() throws ServletException 173 { 174 try 175 { 176 _runningTask.execute(repositoryInfo); 177 } 178 catch (RepositoryException e) 179 { 180 logger.error(e.getMessage(), e); 181 } 182 finally 183 { 184 // Currently repository cannot be restarted, the Ametys application have to be reloaded. 185 // So the REPOSITORY_UNAVAILABLE system status cannot be removed 186 // _systemStatus.removeStatus(REPOSITORY_UNAVAILABLE); 187 _systemStatus.removeStatus(MAINTENANCE_TASK_RUNNING); 188 _systemStatus.addStatus(MAINTENANCE_TASK_ENDED); 189 _runningTaskType = null; 190 } 191 192 return null; 193 } 194 }); 195 196 _executor.execute(_future); 197 } 198 199 /** 200 * Retrieve the repository config if no repository is available or the repository context of the available repository 201 * @return Either the config or the context depending if the repository is available. The other one will be null. 202 * @throws ConfigurationException if an error occurred while generating the config 203 * @throws UnsupportedRepositoryOperationException if the repository doesn't support retrieving the context 204 */ 205 protected Pair<RepositoryConfig, RepositoryContext> _getRepositoryInfo() throws ConfigurationException, UnsupportedRepositoryOperationException 206 { 207 final RepositoryConfig repositoryConfig; 208 final RepositoryContext repositoryContext; 209 210 if (_repositoryProvider == null) 211 { 212 repositoryConfig = _createRepositoryConfig(); 213 repositoryContext = null; 214 } 215 else 216 { 217 _runningTaskType = null; 218 throw new UnsupportedRepositoryOperationException("A maintenance task can not be run on an online repository instance of " + _repositoryProvider.getRepository().getClass().getName()); 219 } 220 return Pair.of(repositoryConfig, repositoryContext); 221 } 222 223 /** 224 * Create a repository config 225 * @return the repository config 226 * @throws ConfigurationException if an error occurred 227 */ 228 protected RepositoryConfig _createRepositoryConfig() throws ConfigurationException 229 { 230 String config = _context.getRealPath("/WEB-INF/param/repository.xml"); 231 232 File homeFile = new File(AmetysHomeHelper.getAmetysHomeData(), "repository"); 233 234 if (getLogger().isDebugEnabled()) 235 { 236 getLogger().debug("Creating JCR Repository config at: " + homeFile.getAbsolutePath()); 237 } 238 239 return RepositoryConfig.create(config, homeFile.getAbsolutePath()); 240 } 241 242 /** 243 * Initialize the tasks. 244 * @param type MaintenanceTaskType 245 * @return the task. 246 */ 247 protected AbstractMaintenanceTask _createTask(MaintenanceTaskType type) 248 { 249 switch (type) 250 { 251 case REMOVE_UNUSED_HISTORY: //online + renforce pour 252 return new RemoveUnusedHistoryTask(); 253 case DATA_STORE_GARBAGE_COLLECTOR: 254 return new DataStoreGarbageCollectorTask(); 255 case REINDEXING: // offline 256 return new ReindexingTask(); 257 case CONSISTENCY_CHECK: // ?? 258 return new ConsistencyCheckTask(); 259 default: 260 throw new IllegalArgumentException("This type of maintenance task is not allowed : " + type); 261 } 262 } 263 264 /** 265 * Indicates if a maintenance task is running. 266 * @return true if a task is running. 267 */ 268 public boolean isTaskRunning() 269 { 270 return _runningTaskType != null; 271 } 272 273 /** 274 * Get progress information. 275 * @return a map containing information on the progress status. 276 */ 277 public Map<String, Object> getProgressInfo() 278 { 279 if (_runningTask != null) 280 { 281 return _runningTask.getProgressInfo(); 282 } 283 284 return null; 285 } 286 287 /** 288 * Retrieves the type of the running task 289 * @return the type of the running task or null. 290 */ 291 public String getRunningTaskType() 292 { 293 if (_runningTaskType != null) 294 { 295 return _runningTaskType.name(); 296 } 297 298 return null; 299 } 300 301 /** 302 * Retrieves the type of the previous running task, if no task is currently running. 303 * Otherwise, the current task type will be return. 304 * @return the type or null 305 */ 306 public String getLastRunningTaskType() 307 { 308 if (_lastRunningTaskType != null) 309 { 310 return _lastRunningTaskType.name(); 311 } 312 313 return null; 314 } 315 316 /** 317 * Indicates is the Ametys repository is started or has been shut down. 318 * @return true is the repository is started 319 */ 320 public boolean isRepositoryStarted() 321 { 322 return !_repositoryShutdown; 323 } 324}