blob: 9ce195306bd7a7c4e6730367a68eef5525a99469 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.datacache;
import java.security.AccessController;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.Clearable;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.util.InvalidStateException;
import org.apache.openjpa.util.UserException;
/**
* Cron-style clearable eviction. Understands schedules based on cron format:
* <li><code>minute hour mday month wday</code></li>
* <li><code>+minute</code></li>
* For example:
* <code>15,30 6,19 2,10 1 2 </code>
* Would run at 15 and 30 past the 6AM and 7PM, on the 2nd and 10th
* of January when its a Monday.
*
*/
public class ClearableScheduler implements Runnable {
private static final Localizer _loc = Localizer.forPackage(ClearableScheduler.class);
private Map<Clearable,Schedule> _clearables = new ConcurrentHashMap<>();
private boolean _stop = false;
private int _interval = 1;
private Log _log;
private Thread _thread;
public ClearableScheduler(OpenJPAConfiguration conf) {
_log = conf.getLogFactory().getLog(OpenJPAConfiguration.LOG_DATACACHE);
}
/**
* The interval time in minutes between scheduler checks. Defaults to 1.
*/
public int getInterval() {
return _interval;
}
/**
* The interval time in minutes between scheduler checks. Defaults to 1.
*/
public void setInterval(int interval) {
_interval = interval;
}
/**
* Stop the associated thread if there and stop the current runnable.
*/
public synchronized void stop() {
_stop = true;
}
private boolean isStopped() {
return _stop;
}
/**
* Schedule the given Clearable for clear to be called. Starts the scheduling thread
* if not started.
*/
public synchronized void scheduleEviction(Clearable clearable, String times) {
if (times == null)
return;
Schedule schedule = new Schedule(times);
_clearables.put(clearable, schedule);
_stop = false;
if (_thread == null) {
_thread =
AccessController.doPrivileged(J2DoPrivHelper
.newDaemonThreadAction(this, _loc.get("scheduler-name")
.getMessage()));
_thread.start();
if (_log.isTraceEnabled())
_log.trace(_loc.get("scheduler-start", _thread.getName()));
}
}
/**
* Remove the given Clearable from scheduling.
*/
public synchronized void removeFromSchedule(Clearable clearable) {
_clearables.remove(clearable);
if (_clearables.size() == 0)
stop();
}
@Override
public void run() {
if (_log.isTraceEnabled())
_log.trace(_loc.get("scheduler-interval", _interval + ""));
Date lastRun = new Date();
DateFormat fom = new SimpleDateFormat("E HH:mm:ss");
while (!isStopped()) {
try {
Thread.sleep(_interval * 60 * 1000);
Date now = new Date();
for(Entry<Clearable, Schedule> entry : _clearables.entrySet()){
Clearable clearable = entry.getKey();
Schedule schedule = entry.getValue();
if (schedule.matches(lastRun, now)) {
if (_log.isTraceEnabled())
_log.trace(_loc.get("scheduler-clear", clearable, fom.format(now)));
evict(clearable);
}
}
lastRun = now;
} catch (Exception e) {
throw new InvalidStateException(_loc.get("scheduler-fail"), e).
setFatal(true);
}
}
_log.info(_loc.get("scheduler-stop"));
synchronized (this) {
if (isStopped())
_thread = null; // be sure to deref the thread so it can restart
}
}
protected void evict(Clearable cache) {
cache.clear();
}
/**
* Simple class which represents the given time schedule.
*/
private static class Schedule {
static final int[] WILDCARD = new int[0];
static final int[] UNITS = {
Calendar.MONTH,
Calendar.DAY_OF_MONTH,
Calendar.DAY_OF_WEEK,
Calendar.HOUR_OF_DAY,
Calendar.MINUTE
};
final int[] month;
final int[] dayOfMonth;
final int[] dayOfWeek;
final int[] hour;
final int[] min;
public Schedule(String date) {
int[] tmin = null;
if (date.startsWith("+")) {
Calendar cal = Calendar.getInstance();
int interval = Integer.parseInt(date.substring(1));
int currMin = cal.get(Calendar.MINUTE);
tmin = new int[60/interval];
for(int i = 0; i<tmin.length;i++){
int temp;
if(i==0){
temp=currMin+interval;
}else{
temp=tmin[i-1]+interval;
}
if(temp >= 60 ){
temp -= 60;
}
tmin[i]=temp;
}
Arrays.sort(tmin);
min = tmin;
hour = WILDCARD;
dayOfMonth = WILDCARD;
month = WILDCARD;
dayOfWeek = WILDCARD;
}else{
StringTokenizer token = new StringTokenizer(date, " \t");
if (token.countTokens() != 5)
throw new UserException(_loc.get("bad-count", date)).setFatal(true);
try {
min = parse(token.nextToken(), 0, 60);
hour = parse(token.nextToken(), 0, 24);
dayOfMonth = parse(token.nextToken(), 1, 31);
month = parse(token.nextToken(), 1, 13);
dayOfWeek = parse(token.nextToken(), 1, 8);
} catch (Throwable t) {
throw new UserException(_loc.get("bad-schedule", date), t).setFatal(true);
}
}
}
private int[] parse(String token, int min, int max) {
if ("*".equals(token.trim()))
return WILDCARD;
String[] tokens = StringUtil.split(token, ",", 0);
int [] times = new int[tokens.length];
for (int i = 0; i < tokens.length; i++) {
try {
times[i] = Integer.parseInt(tokens[i]);
} catch (Throwable t) {
throw new UserException(_loc.get("not-number", token)).
setFatal(true);
}
if (times[i] < min || times[i] >= max)
throw new UserException(_loc.get("not-range", token,
String.valueOf(min), String.valueOf(max))).
setFatal(true);
}
return times;
}
boolean matches(Date last, Date now) {
Calendar time = Calendar.getInstance();
time.setTime(now);
time.set(Calendar.SECOND, 0);
time.set(Calendar.MILLISECOND, 0);
int[][] all =
new int[][]{ month, dayOfMonth, dayOfWeek, hour, min };
return matches(last, now, time, all, 0);
}
private boolean matches(Date last, Date now, Calendar time,
int[][] times, int depth) {
if (depth == UNITS.length) {
Date compare = time.getTime();
return compare.compareTo(last) >= 0 &&
compare.compareTo(now) < 0;
}
if (times[depth] != WILDCARD) {
for (int i = 0; i < times[depth].length; i++) {
time.set(UNITS[depth], times[depth][i]);
if (matches(last, now, time, times, depth + 1))
return true;
}
}
return matches(last, now, time, times, depth + 1);
}
}
}