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