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