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