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