OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 132 | Go to most recent revision | Details | Compare with Previous | 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
 
180 ilm 110
        boolean interrupted = false;
132 ilm 111
        if (didDie) {
112
            // only CacheTimeOut are in our executor (plus the runnable for trimWatchers())
113
            // and all items in a cache (even the running ones) have a timeout (but they don't all
114
            // have a watcher : watcherFactory can be null and an item can have no data)
115
            for (final Runnable r : runnables) {
116
                final IScheduledFutureTask<?> sft = (IScheduledFutureTask<?>) r;
117
                if (sft.getInner() instanceof CacheTimeOut) {
118
                    sft.getInnerRunnable().run();
119
                }
120
            }
121
 
180 ilm 122
            // make sure that the currently executing runnable is done before checking
123
            while (true) {
124
                try {
125
                    if (this.getTimer().awaitTermination(1, TimeUnit.SECONDS))
126
                        break;
127
                } catch (InterruptedException e) {
128
                    interrupted = true;
129
                }
130
            }
132 ilm 131
            synchronized (this) {
132
                purgeWatchers();
133
                assert this.watchers.isEmpty() : this.watchers.size() + " item(s) were not removed : " + this.watchers.values();
134
            }
135
        }
136
 
180 ilm 137
        if (interrupted)
138
            Thread.currentThread().interrupt();
132 ilm 139
        return didDie;
140
    }
141
 
142
    public final boolean isDying() {
143
        return this.getTimer().isShutdown();
144
    }
145
 
146
    final ScheduledExecutorService getTimer() {
147
        return this.timer;
148
    }
149
 
150
    public final void purgeTimer() {
151
        this.timer.purge();
152
    }
153
 
154
    public synchronized void setWatcherFactory(CacheWatcherFactory<? super D> watcherFactory) {
155
        if (watcherFactory == null)
156
            throw new NullPointerException("Null factory");
157
        if (this.watcherFactory != null)
158
            throw new IllegalStateException("Already set to " + this.watcherFactory);
159
        this.watcherFactory = watcherFactory;
160
    }
161
 
162
    final synchronized CacheWatcher<? super D> watch(D data, final CacheItem<?, ?, D> item) {
163
        if (this.watcherFactory == null)
164
            return null;
165
        if (this.isDying())
166
            throw new RejectedExecutionException("Dead support");
167
        CacheWatcher<? super D> watcher = this.watchers.get(data);
168
        if (watcher == null) {
169
            try {
170
                watcher = this.watcherFactory.createWatcher(data);
171
            } catch (Exception e) {
172
                throw new IllegalStateException("Couldn't create watcher for " + data, e);
173
            }
174
            this.watchers.put(data, watcher);
175
        }
176
        watcher.add(item);
177
        return watcher;
178
    }
179
 
180
    final synchronized Map<D, CacheWatcher<? super D>> watch(Set<? extends D> data, final CacheItem<?, ?, D> item) {
181
        final Map<D, CacheWatcher<? super D>> res = new LinkedHashMap<D, CacheWatcher<? super D>>(data.size(), 1.0f);
182
        for (final D d : data) {
183
            final CacheWatcher<? super D> watcher = this.watch(d, item);
184
            if (watcher != null)
185
                res.put(d, watcher);
186
        }
187
        return Collections.unmodifiableMap(res);
188
    }
189
 
190
    public final synchronized int getWatchersCount() {
191
        return this.watchers.size();
192
    }
193
 
194
    public final synchronized int purgeWatchers() {
195
        final Iterator<Entry<D, CacheWatcher<? super D>>> iter = this.watchers.entrySet().iterator();
196
        while (iter.hasNext()) {
197
            final Entry<D, CacheWatcher<? super D>> e = iter.next();
198
            if (e.getValue().isEmpty())
199
                iter.remove();
200
        }
201
        return this.getWatchersCount();
202
    }
203
 
204
    final synchronized boolean dependsOn(D data) {
205
        return this.watchers.containsKey(data) && !this.watchers.get(data).isEmpty();
206
    }
207
}