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.getValueAsBoolean(__ACTIVATE_PARAMETER).booleanValue(); 095 096 if (_activated) 097 { 098 _period = config.getValueAsLong(__PERIOD_PARAMETER).longValue(); 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 if (getLogger().isErrorEnabled()) 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 /** 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 if (getLogger().isErrorEnabled()) 242 { 243 getLogger().error("An error occured when trying to schedule the unlocking of the object " + object.getId(), e); 244 } 245 } 246 return false; 247 } 248 249 /** 250 * Get all current locked {@link LockableAmetysObject}. 251 * @param <A> the generic type extending {@link LockableAmetysObject}. 252 * @return The contents as a Map of Content to LocalDateTime (the date time when they were locked). 253 */ 254 @SuppressWarnings("unchecked") 255 public <A extends LockableAmetysObject> Map<A, LocalDateTime> getLockedObjects() 256 { 257 Map<A, LocalDateTime> result = new HashMap<>(); 258 259 synchronized (_lockedObjects) 260 { 261 for (Entry<String, LocalDateTime> entry : _lockedObjects.entrySet()) 262 { 263 try 264 { 265 LockableAmetysObject object = _resolver.resolveById(entry.getKey()); 266 267 if (object.isLocked()) 268 { 269 // Add the current object and its locking time. 270 // We test if it's still locked because it could have been manually unlocked in the meantime. 271 result.put((A) object, entry.getValue()); 272 } 273 } 274 catch (UnknownAmetysObjectException e) 275 { 276 // The object doesn't exist anymore: remove it from the map. 277 if (getLogger().isWarnEnabled()) 278 { 279 getLogger().warn("The object of ID " + entry.getKey() + " is referenced as locked but it doesn't exist anymore.", e); 280 } 281 _lockedObjects.remove(entry.getKey()); 282 } 283 284 } 285 } 286 287 return result; 288 } 289 290 @Override 291 public void dispose() 292 { 293 _activated = false; 294 295 setLogger(null); 296 _resolver = null; 297 _lockedObjects = null; 298 } 299 300}