| /* |
| * 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.Calendar; |
| import java.util.Date; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.StringTokenizer; |
| |
| import org.apache.openjpa.conf.OpenJPAConfiguration; |
| import org.apache.openjpa.lib.log.Log; |
| import org.apache.openjpa.lib.util.J2DoPrivHelper; |
| import org.apache.openjpa.lib.util.Localizer; |
| import java.util.concurrent.ConcurrentHashMap; |
| import org.apache.openjpa.util.InvalidStateException; |
| import org.apache.openjpa.util.UserException; |
| import serp.util.Strings; |
| |
| /** |
| * Cron-style cache eviction. Understands schedules based on cron format: |
| * <code>minute hour mday month wday</code> |
| * 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. |
| * |
| * @author Steve Kim |
| */ |
| public class DataCacheScheduler |
| implements Runnable { |
| |
| private static final Localizer _loc = Localizer.forPackage |
| (DataCacheScheduler.class); |
| |
| private Map _caches = new ConcurrentHashMap(); |
| private boolean _stop = false; |
| private int _interval = 2; |
| private Log _log; |
| private Thread _thread; |
| |
| public DataCacheScheduler(OpenJPAConfiguration conf) { |
| _log = conf.getLogFactory().getLog(OpenJPAConfiguration.LOG_DATACACHE); |
| } |
| |
| /** |
| * The interval time in minutes between cache checks. Defaults to 2. |
| */ |
| public int getInterval() { |
| return _interval; |
| } |
| |
| /** |
| * The interval time in minutes between cache checks. Defaults to 2. |
| */ |
| 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 cache for eviction. Starts the scheduling thread |
| * if not started. |
| */ |
| public synchronized void scheduleEviction(DataCache cache, String times) { |
| if (times == null) |
| return; |
| |
| Schedule schedule = new Schedule(times); |
| _caches.put(cache, schedule); |
| _stop = false; |
| if (_thread == null) { |
| _thread = (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 cache from scheduling. |
| */ |
| public synchronized void removeFromSchedule(DataCache cache) { |
| _caches.remove(cache); |
| if (_caches.size() == 0) |
| stop(); |
| } |
| |
| 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(); |
| DataCache cache; |
| Schedule schedule; |
| Map.Entry entry; |
| for (Iterator i = _caches.entrySet().iterator(); i.hasNext();) { |
| entry = (Map.Entry) i.next(); |
| cache = (DataCache) entry.getKey(); |
| schedule = (Schedule) entry.getValue(); |
| if (schedule.matches(lastRun, now)) { |
| if (_log.isTraceEnabled()) |
| _log.trace(_loc.get("scheduler-clear", |
| cache.getName(), fom.format(now))); |
| evict(cache); |
| } |
| } |
| 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(DataCache 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) { |
| 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 = Strings.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); |
| } |
| } |
| } |