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