1 | /**
|
---|
2 | * EntityLoadManager.java
|
---|
3 | * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
|
---|
4 | *
|
---|
5 | * This program is free software: you can redistribute it and/or modify
|
---|
6 | * it under the terms of the GNU General Public License as published by
|
---|
7 | * the Free Software Foundation, either version 3 of the License, or
|
---|
8 | * (at your option) any later version.
|
---|
9 | *
|
---|
10 | * This program is distributed in the hope that it will be useful,
|
---|
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
---|
13 | * GNU General Public License for more details.
|
---|
14 | *
|
---|
15 | * You should have received a copy of the GNU General Public License
|
---|
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
---|
17 | */
|
---|
18 |
|
---|
19 | package org.expeditee.taskmanagement;
|
---|
20 |
|
---|
21 | import java.util.HashMap;
|
---|
22 | import java.util.PriorityQueue;
|
---|
23 |
|
---|
24 |
|
---|
25 |
|
---|
26 | /**
|
---|
27 | * Manages asynchronous loading. It is essentially a daemon that performs all expensive proccessing
|
---|
28 | * for a frame.
|
---|
29 | *
|
---|
30 | * The Objective / Purpose:
|
---|
31 | * To load data on Expeditee frames which can take long periods of time in a way such that
|
---|
32 | * navigating through frames with lots of data is smooth / fast.
|
---|
33 | *
|
---|
34 | * This helps reduce vital usability concerns with frame load times - a conern of high importance
|
---|
35 | * in KMS publications...
|
---|
36 | *
|
---|
37 | * @author Brook Novak
|
---|
38 | *
|
---|
39 | */
|
---|
40 | public final class EntityLoadManager {
|
---|
41 |
|
---|
42 | /**
|
---|
43 | * Singleton design pattern
|
---|
44 | */
|
---|
45 | private static EntityLoadManager _instance = new EntityLoadManager();
|
---|
46 | public static EntityLoadManager getInstance() {
|
---|
47 | return _instance;
|
---|
48 | }
|
---|
49 |
|
---|
50 | /**
|
---|
51 | * Start daemon right away
|
---|
52 | */
|
---|
53 | private EntityLoaderDaemon loadDaemon;
|
---|
54 | private EntityLoadManager() {
|
---|
55 | loadDaemon = new EntityLoaderDaemon();
|
---|
56 | loadDaemon.start();
|
---|
57 | }
|
---|
58 |
|
---|
59 | private PriorityQueue<QueuedEntity> loadQueue = new PriorityQueue<QueuedEntity>();
|
---|
60 |
|
---|
61 | private HashMap<LoadableEntity, QueuedEntity> queueMap = new HashMap<LoadableEntity, QueuedEntity>();
|
---|
62 |
|
---|
63 | private QueuedEntity busyEntity = null;
|
---|
64 |
|
---|
65 | private Object entityLocker = new Object(); // union of queueMap, queueMap and busyEntity resources
|
---|
66 |
|
---|
67 | /**
|
---|
68 | * Queue's an entity for loading.
|
---|
69 | * This operation is threadsafe.
|
---|
70 | *
|
---|
71 | * @param entity
|
---|
72 | * The entity to load.
|
---|
73 | *
|
---|
74 | * @param minDelay
|
---|
75 | * The minimum time to wait before loading in milliseconds.
|
---|
76 | *
|
---|
77 | * @return
|
---|
78 | * True if the entity was added. False if the entity is already queued prior to this call.
|
---|
79 | * Note that if entity is currently loading, it will still be added to the queue, this
|
---|
80 | * allows for requeueing a busy entity.
|
---|
81 | *
|
---|
82 | */
|
---|
83 | public boolean queue(LoadableEntity entity, int minDelay) {
|
---|
84 | synchronized(entityLocker) {
|
---|
85 |
|
---|
86 | if (queueMap.containsKey(entity))
|
---|
87 | return false;
|
---|
88 |
|
---|
89 | QueuedEntity qe = new QueuedEntity(entity, (int)minDelay, 0);
|
---|
90 | loadQueue.add(qe);
|
---|
91 | queueMap.put(entity, qe);
|
---|
92 | }
|
---|
93 |
|
---|
94 | // Ensure daemon knows of new item
|
---|
95 | loadDaemon.reschedule();
|
---|
96 |
|
---|
97 | return true;
|
---|
98 | }
|
---|
99 |
|
---|
100 | /**
|
---|
101 | * Increasing a priority on an entity will invalidate the wait-time
|
---|
102 | * until it is loaded and push it up the load-queue.
|
---|
103 | *
|
---|
104 | * Proceeding calls to this on the same LoadableEntity will cumatively
|
---|
105 | * increase its priority so that it will continually be pushed up in the
|
---|
106 | * priority queue.
|
---|
107 | *
|
---|
108 | * This operation is threadsafe.
|
---|
109 | *
|
---|
110 | * @param entity The entity to increase its priority
|
---|
111 | */
|
---|
112 | public void increasePriority(LoadableEntity entity) {
|
---|
113 |
|
---|
114 | synchronized(entityLocker) {
|
---|
115 |
|
---|
116 | QueuedEntity qe = queueMap.remove(entity);
|
---|
117 | loadQueue.remove(qe);
|
---|
118 |
|
---|
119 | if (qe != null) {
|
---|
120 | qe = qe.higherPriority();
|
---|
121 | loadQueue.add(qe);
|
---|
122 | queueMap.put(entity, qe);
|
---|
123 | }
|
---|
124 |
|
---|
125 | }
|
---|
126 |
|
---|
127 | // Ensure daemon knows of new item
|
---|
128 | loadDaemon.reschedule();
|
---|
129 |
|
---|
130 | }
|
---|
131 |
|
---|
132 | /**
|
---|
133 | * Cancels all loadable entities on the queue.
|
---|
134 | * If an entity is currently being loaded that
|
---|
135 | *
|
---|
136 | * Note that it is up to the entities descretion to stop loading.
|
---|
137 | * As far as the LoadTimeManager is concerned all entities are cancelled
|
---|
138 | * and the queue is emptied.
|
---|
139 | *
|
---|
140 | * This operation is threadsafe.
|
---|
141 | */
|
---|
142 | public void cancelAll() {
|
---|
143 |
|
---|
144 | synchronized(entityLocker) {
|
---|
145 |
|
---|
146 | // Notify all entities that they are cancelled
|
---|
147 | for (QueuedEntity qe : loadQueue) {
|
---|
148 | qe.entity.cancelLoadRequested();
|
---|
149 | }
|
---|
150 |
|
---|
151 | // Clear queue / map
|
---|
152 | loadQueue.clear();
|
---|
153 | queueMap.clear();
|
---|
154 |
|
---|
155 | // Cancel current entity if applicable
|
---|
156 | if (busyEntity != null) {
|
---|
157 | busyEntity.entity.cancelLoadRequested();
|
---|
158 | busyEntity = null;
|
---|
159 | }
|
---|
160 | }
|
---|
161 | }
|
---|
162 |
|
---|
163 | /**
|
---|
164 | * Cancels a specific loadable entity from loading. Removes it from the queue
|
---|
165 | *
|
---|
166 | * @param entity
|
---|
167 | */
|
---|
168 | public void cancel(LoadableEntity entity) {
|
---|
169 |
|
---|
170 | synchronized(entityLocker) {
|
---|
171 | QueuedEntity qe = queueMap.remove(entity);
|
---|
172 | if (qe != null) {
|
---|
173 | loadQueue.remove(qe);
|
---|
174 | } else if (busyEntity != null && busyEntity.entity == entity) {
|
---|
175 | busyEntity.entity.cancelLoadRequested();
|
---|
176 | busyEntity = null;
|
---|
177 | }
|
---|
178 | }
|
---|
179 |
|
---|
180 | }
|
---|
181 |
|
---|
182 | private class EntityLoaderDaemon extends Thread {
|
---|
183 |
|
---|
184 | private Object idleLocker = new Object(); // Daemon sleeps on this
|
---|
185 |
|
---|
186 | public EntityLoaderDaemon() {
|
---|
187 | super("EntityLoaderDaemon");
|
---|
188 | setDaemon(true);
|
---|
189 | }
|
---|
190 |
|
---|
191 | /**
|
---|
192 | * Invoke to wake up the daemon and recheck the queue
|
---|
193 | */
|
---|
194 | public void reschedule() {
|
---|
195 | synchronized(idleLocker) {
|
---|
196 | idleLocker.notify();
|
---|
197 | }
|
---|
198 | }
|
---|
199 |
|
---|
200 | @Override
|
---|
201 | public void run() {
|
---|
202 |
|
---|
203 | while (true) { // continually load whatever needs loading (daemon)
|
---|
204 |
|
---|
205 | // See if there is an item in the load queu
|
---|
206 | long waitTime = -1;
|
---|
207 | QueuedEntity qe = null;
|
---|
208 | synchronized(entityLocker) {
|
---|
209 |
|
---|
210 | assert (busyEntity == null);
|
---|
211 | busyEntity = loadQueue.peek();
|
---|
212 |
|
---|
213 | if (busyEntity != null) {
|
---|
214 | // See if it is time to load the entity
|
---|
215 | waitTime = busyEntity.loadTime - System.currentTimeMillis();
|
---|
216 | if (busyEntity.priority > 0 || waitTime <= 0) {
|
---|
217 | loadQueue.remove(); // remove the entity
|
---|
218 | queueMap.remove(busyEntity.entity);
|
---|
219 | qe = busyEntity;
|
---|
220 | } else {
|
---|
221 | busyEntity = null; // not ready to load yet
|
---|
222 | }
|
---|
223 | }
|
---|
224 |
|
---|
225 | }
|
---|
226 |
|
---|
227 | // If there is nothing to load
|
---|
228 | if (qe == null) {
|
---|
229 | try {
|
---|
230 | synchronized(idleLocker) { // obtain idleLocker's monitor
|
---|
231 | if (waitTime > 0) { // if there is a entity to load in a few momenets, wait until delay elapsed
|
---|
232 | idleLocker.wait(waitTime);
|
---|
233 | } else { // If there is nothing to load, then wait until something is added
|
---|
234 | idleLocker.wait();
|
---|
235 | }
|
---|
236 | }
|
---|
237 | } catch (InterruptedException e) { // Something added/re-prioritized.
|
---|
238 | /* Consume */
|
---|
239 | }
|
---|
240 |
|
---|
241 | } else { // load a queued entity
|
---|
242 |
|
---|
243 | try {
|
---|
244 | qe.entity.performLoad();
|
---|
245 | } catch (Exception e) { // safety
|
---|
246 | e.printStackTrace();
|
---|
247 | }
|
---|
248 |
|
---|
249 | synchronized(entityLocker) { // Not busy anymore
|
---|
250 | busyEntity = null;
|
---|
251 | }
|
---|
252 |
|
---|
253 | }
|
---|
254 |
|
---|
255 | } // continue running daemon
|
---|
256 | }
|
---|
257 | }
|
---|
258 |
|
---|
259 | /**
|
---|
260 | * Used for prioritized queue
|
---|
261 | *
|
---|
262 | * @author Brook
|
---|
263 | */
|
---|
264 | private class QueuedEntity implements Comparable {
|
---|
265 |
|
---|
266 | QueuedEntity(LoadableEntity entity, int minDelay, int priority) {
|
---|
267 | this.entity = entity;
|
---|
268 | this.priority = priority;
|
---|
269 | loadTime = System.currentTimeMillis() + minDelay;
|
---|
270 | }
|
---|
271 |
|
---|
272 | private QueuedEntity(LoadableEntity entity, long loadTime, int priority) {
|
---|
273 | this.entity = entity;
|
---|
274 | this.priority = priority;
|
---|
275 | this.loadTime = loadTime;
|
---|
276 | }
|
---|
277 |
|
---|
278 | public QueuedEntity higherPriority() {
|
---|
279 | return new QueuedEntity(entity, loadTime, priority + 1);
|
---|
280 | }
|
---|
281 |
|
---|
282 | private LoadableEntity entity;
|
---|
283 | private long loadTime;
|
---|
284 | private int priority;
|
---|
285 |
|
---|
286 | public int compareTo(Object obj) {
|
---|
287 | QueuedEntity other = (QueuedEntity)obj;
|
---|
288 | if (priority == other.priority) {
|
---|
289 | return new Long(loadTime).compareTo(other.loadTime);
|
---|
290 | } else if (priority < 0) {
|
---|
291 | return -1;
|
---|
292 | }
|
---|
293 | return 1;
|
---|
294 |
|
---|
295 | }
|
---|
296 |
|
---|
297 | @Override
|
---|
298 | public int hashCode() {
|
---|
299 | return entity.hashCode();
|
---|
300 | }
|
---|
301 |
|
---|
302 | @Override
|
---|
303 | public boolean equals(Object obj) {
|
---|
304 | return entity.equals(obj);
|
---|
305 | }
|
---|
306 | }
|
---|
307 |
|
---|
308 |
|
---|
309 | }
|
---|