001/* 002 * Copyright 2010 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.plugins.repository.lock; 017 018import java.time.LocalDateTime; 019import java.time.ZonedDateTime; 020import java.util.HashMap; 021import java.util.Map; 022import java.util.Map.Entry; 023import java.util.Optional; 024 025import org.apache.avalon.framework.activity.Disposable; 026import org.apache.avalon.framework.activity.Initializable; 027import org.apache.avalon.framework.component.Component; 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.avalon.framework.service.Serviceable; 031import org.apache.avalon.framework.thread.ThreadSafe; 032import org.apache.jackrabbit.JcrConstants; 033import org.quartz.JobKey; 034import org.quartz.SchedulerException; 035 036import org.ametys.core.schedule.Runnable; 037import org.ametys.core.user.UserIdentity; 038import org.ametys.core.user.population.UserPopulationDAO; 039import org.ametys.plugins.core.schedule.Scheduler; 040import org.ametys.plugins.repository.AmetysObject; 041import org.ametys.plugins.repository.AmetysObjectIterable; 042import org.ametys.plugins.repository.AmetysObjectResolver; 043import org.ametys.plugins.repository.UnknownAmetysObjectException; 044import org.ametys.runtime.config.Config; 045import org.ametys.runtime.plugin.PluginsManager; 046import org.ametys.runtime.plugin.component.AbstractLogEnabled; 047 048/** 049 * The helper for scheduling the unlocking of the contents.<br> 050 * At initialization all contents are scanned and locked ones will be automatically unlocked. 051 */ 052public class UnlockHelper extends AbstractLogEnabled implements Initializable, ThreadSafe, Component, Serviceable, Disposable 053{ 054 /** Avalon Role */ 055 public static final String ROLE = UnlockHelper.class.getName(); 056 057 // Deploy parameters 058 private static final String __ACTIVATE_PARAMETER = "content.unlocktimer.activate"; 059 060 private static final String __PERIOD_PARAMETER = "content.unlocktimer.period"; 061 062 /** The scheduler */ 063 protected Scheduler _scheduler; 064 065 // To know if the component is activated 066 private boolean _activated; 067 068 // The period before unlocking in hours 069 private long _period; 070 071 /** The map of locked contents id (String) and their locked time (value) */ 072 private Map<String, LocalDateTime> _lockedObjects; 073 074 private AmetysObjectResolver _resolver; 075 076 public void service(ServiceManager manager) throws ServiceException 077 { 078 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 079 _scheduler = (Scheduler) manager.lookup(Scheduler.ROLE); 080 } 081 082 public void initialize() throws Exception 083 { 084 Config config = Config.getInstance(); 085 if (config == null || PluginsManager.getInstance().isSafeMode()) 086 { 087 // Désactiver le composant pour affecter les paramètres de 088 // déploiement 089 // Le composant doit pouvoir s'initialiser en safe mode, mais ne pas 090 // lancer le timer. 091 _activated = false; 092 } 093 else 094 { 095 // Récupérer les paramètres de déploiement 096 _activated = config.getValue(__ACTIVATE_PARAMETER); 097 098 if (_activated) 099 { 100 _period = config.getValue(__PERIOD_PARAMETER); 101 102 // Vérifier la validité de la période 103 if (_period <= 0) 104 { 105 throw new RuntimeException("Invalid period (in hours) : '" + _period + "'"); 106 } 107 } 108 else 109 { 110 _period = 0; 111 } 112 } 113 114 _initializeLockedObjects(); 115 } 116 117 /** 118 * Search and stores references to all locked {@link AmetysObject}s in the repository.<br> 119 * Should not be called by clients. 120 */ 121 private void _initializeLockedObjects() 122 { 123 _lockedObjects = new HashMap<>(); 124 125 // search for all locked AmetysObjects 126 try (AmetysObjectIterable<LockableAmetysObject> it = _resolver.query("//element(*, ametys:object)[@" + JcrConstants.JCR_LOCKOWNER + "]");) 127 { 128 for (LockableAmetysObject object : it) 129 { 130 if (object.isLocked() && _activated) 131 { 132 scheduleUnlocking(object); 133 getLogger().info("Scheduled unlocking in " + _period + " hour(s) for object '{}'", object.getName()); 134 } 135 else if (object.isLocked() && !_activated) 136 { 137 // If the unlocking was activated with remaining locked contents, and now it is disabled 138 // there are stil remaining quartz job for unlocking those contents 139 synchronized (_lockedObjects) 140 { 141 _lockedObjects.put(object.getId(), LocalDateTime.now()); 142 } 143 _cancelUnlocking(object); 144 } 145 } 146 } 147 } 148 149 /** 150 * Check if the unlocking is enabled. 151 * @return true if the unlocking is activated, false otherwise. 152 */ 153 public boolean isUnlockingActivated() 154 { 155 return _activated; 156 } 157 158 /** 159 * Get the period before unlocking a content. 160 * @return The period in hours. 161 */ 162 public long getTimeBeforeUnlock() 163 { 164 return _period; 165 } 166 167 /** 168 * Schedule a task to unlock an object. 169 * @param object The {@link AmetysObject} to be unlocked. 170 */ 171 public void scheduleUnlocking(AmetysObject object) 172 { 173 synchronized (_lockedObjects) 174 { 175 _lockedObjects.put(object.getId(), LocalDateTime.now()); 176 } 177 178 if (_activated) 179 { 180 try 181 { 182 ZonedDateTime unlockDate = ZonedDateTime.now().plusHours(_period); 183 184 UserIdentity lockUser = Optional.ofNullable(object) 185 .filter(LockableAmetysObject.class::isInstance) 186 .map(LockableAmetysObject.class::cast) 187 .map(LockableAmetysObject::getLockOwner) 188 .orElse(UserPopulationDAO.SYSTEM_USER_IDENTITY); 189 Runnable unlockRunnable = new UnlockRunnable(object.getId(), object.getName(), unlockDate, lockUser); 190 JobKey jobKey = new JobKey(unlockRunnable.getId(), Scheduler.JOB_GROUP); 191 if (_scheduler.getScheduler().checkExists(jobKey)) 192 { 193 _scheduler.getScheduler().deleteJob(jobKey); 194 } 195 _scheduler.scheduleJob(unlockRunnable); 196 getLogger().info("Scheduled unlocking in " + _period + " hour(s) for object '{}'", object.getName()); 197 } 198 catch (SchedulerException e) 199 { 200 getLogger().error("An error occured when trying to schedule the unlocking of the object " + object.getId(), e); 201 } 202 } 203 } 204 205 /** 206 * Cancel the unlocking of an {@link AmetysObject}. 207 * @param object The object to cancel the unlock scheduling. 208 * @return true if the unlocking was cancelled, false otherwise. 209 */ 210 public boolean cancelUnlocking(AmetysObject object) 211 { 212 synchronized (_lockedObjects) 213 { 214 _lockedObjects.remove(object.getId()); 215 } 216 217 if (_activated) 218 { 219 return _cancelUnlocking(object); 220 } 221 else 222 { 223 return false; 224 } 225 } 226 227 private boolean _cancelUnlocking(AmetysObject object) 228 { 229 try 230 { 231 String jobName = UnlockRunnable.class.getName() + "." + object.getId(); 232 JobKey jobKey = new JobKey(jobName, Scheduler.JOB_GROUP); 233 if (_scheduler.getScheduler().checkExists(jobKey)) 234 { 235 _scheduler.getScheduler().deleteJob(jobKey); 236 return true; 237 } 238 } 239 catch (SchedulerException e) 240 { 241 getLogger().error("An error occured when trying to schedule the unlocking of the object " + object.getId(), e); 242 } 243 return false; 244 } 245 246 /** 247 * Get all current locked {@link LockableAmetysObject}. 248 * @param <A> the generic type extending {@link LockableAmetysObject}. 249 * @return The contents as a Map of Content to LocalDateTime (the date time when they were locked). 250 */ 251 @SuppressWarnings("unchecked") 252 public <A extends LockableAmetysObject> Map<A, LocalDateTime> getLockedObjects() 253 { 254 Map<A, LocalDateTime> result = new HashMap<>(); 255 256 synchronized (_lockedObjects) 257 { 258 for (Entry<String, LocalDateTime> entry : _lockedObjects.entrySet()) 259 { 260 try 261 { 262 LockableAmetysObject object = _resolver.resolveById(entry.getKey()); 263 264 if (object.isLocked()) 265 { 266 // Add the current object and its locking time. 267 // We test if it's still locked because it could have been manually unlocked in the meantime. 268 result.put((A) object, entry.getValue()); 269 } 270 } 271 catch (UnknownAmetysObjectException e) 272 { 273 // The object doesn't exist anymore: remove it from the map. 274 if (getLogger().isWarnEnabled()) 275 { 276 getLogger().warn("The object of ID " + entry.getKey() + " is referenced as locked but it doesn't exist anymore.", e); 277 } 278 _lockedObjects.remove(entry.getKey()); 279 } 280 281 } 282 } 283 284 return result; 285 } 286 287 @Override 288 public void dispose() 289 { 290 _activated = false; 291 292 setLogger(null); 293 _resolver = null; 294 _lockedObjects = null; 295 } 296 297}