OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 180 | Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
132 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.utils.cache;
15
 
16
import org.openconcerto.utils.IScheduledFutureTask;
17
 
18
import java.util.Collections;
19
import java.util.HashMap;
20
import java.util.Iterator;
21
import java.util.LinkedHashMap;
22
import java.util.List;
23
import java.util.Map;
24
import java.util.Map.Entry;
25
import java.util.Set;
26
import java.util.concurrent.Callable;
27
import java.util.concurrent.RejectedExecutionException;
28
import java.util.concurrent.RunnableScheduledFuture;
29
import java.util.concurrent.ScheduledExecutorService;
30
import java.util.concurrent.ScheduledThreadPoolExecutor;
31
import java.util.concurrent.ThreadFactory;
32
import java.util.concurrent.TimeUnit;
33
 
34
import net.jcip.annotations.GuardedBy;
35
 
36
/**
37
 * Allow to pool the timer thread and the watchers for multiple {@link ICache}. Pooling the timer
38
 * saves resources, but pooling the watchers makes sure no events are lost when copying a value from
39
 * a cache to another. E.g. a watcher listens to a data, the data changes and copies its listeners
40
 * and begins to notify them. If one listener (before the watcher) hangs for a while, and then a new
41
 * watcher listens to the same data, it will never be notified of the previous event, no matter how
42
 * long ago it was generated. The only other option would be to also listen to the source cache item
43
 * but this is complicated and would need more memory allocation for each item.
44
 *
45
 * @author Sylvain
46
 *
47
 * @param <D> source data type, e.g. SQLTable.
48
 */
49
public final class ICacheSupport<D> {
50
 
51
    private final String name;
52
    private final ScheduledThreadPoolExecutor timer;
53
    @GuardedBy("this")
54
    private CacheWatcherFactory<? super D> watcherFactory;
55
    @GuardedBy("this")
56
    private final Map<D, CacheWatcher<? super D>> watchers;
57
 
58
    public ICacheSupport(final String name) {
59
        this(name, 2, TimeUnit.MINUTES);
60
    }
61
 
62
    public ICacheSupport(final String name, final long amout, final TimeUnit unit) {
63
        this.name = name;
64
        this.timer = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
65
            @Override
66
            public Thread newThread(Runnable r) {
67
                final Thread res = new Thread(r, "cache timeout thread for " + getName());
68
                res.setDaemon(true);
69
                res.setPriority(Thread.MIN_PRIORITY);
70
                return res;
71
            }
72
        }) {
73
            @Override
74
            protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) {
75
                return new IScheduledFutureTask<V>(task).setInnerRunnable(runnable);
76
            }
77
 
78
            @Override
79
            protected <V> RunnableScheduledFuture<V> decorateTask(Callable<V> callable, RunnableScheduledFuture<V> task) {
80
                return new IScheduledFutureTask<V>(task).setInnerCallable(callable);
81
            }
82
        };
83
        this.timer.scheduleWithFixedDelay(new Runnable() {
84
            @Override
85
            public void run() {
86
                purgeWatchers();
87
            }
88
        }, amout, amout, unit);
89
 
90
        this.watcherFactory = null;
91
        this.watchers = new HashMap<D, CacheWatcher<? super D>>();
92
    }
93
 
94
    public final String getName() {
95
        return this.name;
96
    }
97
 
98
    public boolean die() {
99
        final boolean didDie;
100
        final List<Runnable> runnables;
101
        synchronized (this) {
102
            didDie = !this.isDying();
103
            if (didDie) {
104
                runnables = this.getTimer().shutdownNow();
105
            } else {
106
                runnables = null;
107
            }
108
        }
109
 
110
        if (didDie) {
111
            // only CacheTimeOut are in our executor (plus the runnable for trimWatchers())
112
            // and all items in a cache (even the running ones) have a timeout (but they don't all
113
            // have a watcher : watcherFactory can be null and an item can have no data)
114
            for (final Runnable r : runnables) {
115
                final IScheduledFutureTask<?> sft = (IScheduledFutureTask<?>) r;
116
                if (sft.getInner() instanceof CacheTimeOut) {
117
                    sft.getInnerRunnable().run();
118
                }
119
            }
120
 
121
            synchronized (this) {
122
                purgeWatchers();
123
                assert this.watchers.isEmpty() : this.watchers.size() + " item(s) were not removed : " + this.watchers.values();
124
            }
125
        }
126
 
127
        return didDie;
128
    }
129
 
130
    public final boolean isDying() {
131
        return this.getTimer().isShutdown();
132
    }
133
 
134
    final ScheduledExecutorService getTimer() {
135
        return this.timer;
136
    }
137
 
138
    public final void purgeTimer() {
139
        this.timer.purge();
140
    }
141
 
142
    public synchronized void setWatcherFactory(CacheWatcherFactory<? super D> watcherFactory) {
143
        if (watcherFactory == null)
144
            throw new NullPointerException("Null factory");
145
        if (this.watcherFactory != null)
146
            throw new IllegalStateException("Already set to " + this.watcherFactory);
147
        this.watcherFactory = watcherFactory;
148
    }
149
 
150
    final synchronized CacheWatcher<? super D> watch(D data, final CacheItem<?, ?, D> item) {
151
        if (this.watcherFactory == null)
152
            return null;
153
        if (this.isDying())
154
            throw new RejectedExecutionException("Dead support");
155
        CacheWatcher<? super D> watcher = this.watchers.get(data);
156
        if (watcher == null) {
157
            try {
158
                watcher = this.watcherFactory.createWatcher(data);
159
            } catch (Exception e) {
160
                throw new IllegalStateException("Couldn't create watcher for " + data, e);
161
            }
162
            this.watchers.put(data, watcher);
163
        }
164
        watcher.add(item);
165
        return watcher;
166
    }
167
 
168
    final synchronized Map<D, CacheWatcher<? super D>> watch(Set<? extends D> data, final CacheItem<?, ?, D> item) {
169
        final Map<D, CacheWatcher<? super D>> res = new LinkedHashMap<D, CacheWatcher<? super D>>(data.size(), 1.0f);
170
        for (final D d : data) {
171
            final CacheWatcher<? super D> watcher = this.watch(d, item);
172
            if (watcher != null)
173
                res.put(d, watcher);
174
        }
175
        return Collections.unmodifiableMap(res);
176
    }
177
 
178
    public final synchronized int getWatchersCount() {
179
        return this.watchers.size();
180
    }
181
 
182
    public final synchronized int purgeWatchers() {
183
        final Iterator<Entry<D, CacheWatcher<? super D>>> iter = this.watchers.entrySet().iterator();
184
        while (iter.hasNext()) {
185
            final Entry<D, CacheWatcher<? super D>> e = iter.next();
186
            if (e.getValue().isEmpty())
187
                iter.remove();
188
        }
189
        return this.getWatchersCount();
190
    }
191
 
192
    final synchronized boolean dependsOn(D data) {
193
        return this.watchers.containsKey(data) && !this.watchers.get(data).isEmpty();
194
    }
195
}