OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 144 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
182 ilm 4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
17 ilm 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.sql.users;
15
 
16
import org.openconcerto.sql.model.SQLRow;
17
import org.openconcerto.sql.model.SQLRowValues;
18
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
19
import org.openconcerto.sql.model.SQLTable;
20
import org.openconcerto.sql.model.SQLTableEvent;
21
import org.openconcerto.sql.model.SQLTableModifiedListener;
144 ilm 22
import org.openconcerto.utils.CompareUtils;
23
import org.openconcerto.utils.ThreadFactory;
17 ilm 24
 
144 ilm 25
import java.beans.PropertyChangeListener;
26
import java.beans.PropertyChangeSupport;
17 ilm 27
import java.util.ArrayList;
142 ilm 28
import java.util.Collections;
17 ilm 29
import java.util.LinkedHashMap;
30
import java.util.List;
31
import java.util.Map;
144 ilm 32
import java.util.concurrent.Callable;
33
import java.util.concurrent.Executors;
34
import java.util.concurrent.Future;
35
import java.util.concurrent.ScheduledExecutorService;
36
import java.util.concurrent.ScheduledFuture;
37
import java.util.concurrent.TimeUnit;
17 ilm 38
 
142 ilm 39
import net.jcip.annotations.GuardedBy;
40
import net.jcip.annotations.ThreadSafe;
41
 
42
@ThreadSafe
144 ilm 43
public class UserManager implements UserSingleton {
17 ilm 44
 
144 ilm 45
    public static enum State {
46
        CREATED, VALID, DESTROYED
47
    }
48
 
49
    private static final UserSingletonManager<UserManager> sMngr = new UserSingletonManager<UserManager>("USER_COMMON") {
50
        @Override
51
        protected UserManager createInstance(SQLTable t) {
52
            return new UserManager(t).start();
17 ilm 53
        }
144 ilm 54
    };
55
 
56
    public static UserSingletonManager<UserManager> getSingletonManager() {
57
        return sMngr;
17 ilm 58
    }
59
 
144 ilm 60
    public static UserManager setInstance(final SQLTable t) {
61
        return getSingletonManager().setInstance(t);
62
    }
63
 
64
    public static final UserManager getInstance() {
65
        return getSingletonManager().getInstance();
66
    }
67
 
73 ilm 68
    public static final User getUser() {
17 ilm 69
        final UserManager mngr = getInstance();
182 ilm 70
        return getCurrentUser(mngr);
71
    }
72
 
73
    public static User getCurrentUser(final UserManager mngr) {
73 ilm 74
        return mngr == null ? null : mngr.getCurrentUser();
17 ilm 75
    }
76
 
73 ilm 77
    public static final int getUserID() {
78
        final User user = getUser();
79
        return user == null ? SQLRow.NONEXISTANT_ID : user.getId();
80
    }
81
 
144 ilm 82
    // maximum age of DB data
83
    private static final long MAX_AGE_MS = 8 * 60 * 1000;
84
    public static final String CURRENT_USERID_PROPNAME = "currentUserID";
85
    public static final String USERS_PROPNAME = "users";
86
 
17 ilm 87
    private final SQLTable t;
142 ilm 88
    @GuardedBy("this")
89
    private Map<Integer, User> byID;
90
    @GuardedBy("this")
144 ilm 91
    private long timeOfLastFill = -1;
92
    // don't store User because of refresh()
142 ilm 93
    @GuardedBy("this")
144 ilm 94
    private Integer currentUserID;
95
    private final SQLTableModifiedListener tableL;
96
    @GuardedBy("this")
97
    private ScheduledFuture<?> scheduledUpdateUsers;
98
    @GuardedBy("this")
99
    private Future<?> updateUsers;
100
    // a user call has placed a runnable to be executed, don't cancel previously added refresh
101
    @GuardedBy("this")
102
    private Future<?> userFuture;
103
    private final ScheduledExecutorService exec;
17 ilm 104
 
144 ilm 105
    private final PropertyChangeSupport supp = new PropertyChangeSupport(this);
106
 
107
    public UserManager(final SQLTable t) {
108
        this.byID = null;
109
        this.currentUserID = null;
17 ilm 110
        this.t = t;
144 ilm 111
        this.exec = Executors.newScheduledThreadPool(1, new ThreadFactory(UserManager.class.getSimpleName() + " executor for " + t.getSQLName(), true).setPriority(Thread.MIN_PRIORITY));
112
        this.tableL = new SQLTableModifiedListener() {
17 ilm 113
            @Override
114
            public void tableModified(SQLTableEvent evt) {
144 ilm 115
                refresh(true);
17 ilm 116
            }
144 ilm 117
        };
118
    }
119
 
120
    public final UserManager start() {
121
        return this.start(MAX_AGE_MS);
122
    }
123
 
124
    public final synchronized UserManager start(final long maxAge) {
125
        if (this.getState() != State.CREATED)
126
            throw new IllegalStateException("Already started");
127
        this.t.addTableModifiedListener(this.tableL);
17 ilm 128
        fillUsers();
144 ilm 129
        this.scheduledUpdateUsers = this.exec.scheduleWithFixedDelay(new Runnable() {
130
            @Override
131
            public void run() {
132
                refreshIfNeeded(maxAge);
133
            }
134
        }, maxAge / 4, maxAge / 4, TimeUnit.MILLISECONDS);
135
        return this;
17 ilm 136
    }
137
 
144 ilm 138
    public final boolean isValid() {
139
        return getState() == State.VALID;
142 ilm 140
    }
141
 
144 ilm 142
    public synchronized final State getState() {
143
        if (this.scheduledUpdateUsers == null)
144
            return State.CREATED;
145
        else if (this.scheduledUpdateUsers.isDone())
146
            return State.DESTROYED;
147
        else
148
            return State.VALID;
149
    }
150
 
151
    @Override
152
    public synchronized final void destroy() {
153
        final State s = this.getState();
154
        if(s == State.DESTROYED)
155
            return;
156
        if (s == State.VALID) {
157
            this.getTable().removeTableModifiedListener(this.tableL);
158
            this.scheduledUpdateUsers.cancel(true);
159
            this.cancelUpdate();
160
        }
161
        this.exec.shutdown();
162
        assert this.getState() == State.DESTROYED;
163
    }
164
 
165
    private void cancelUpdate() {
166
        assert Thread.holdsLock(this);
167
        if (this.updateUsers != null && (this.userFuture == null || this.userFuture.isDone()))
168
            this.updateUsers.cancel(true);
169
    }
170
 
171
    private boolean isUpdatePending() {
172
        assert Thread.holdsLock(this);
173
        return this.updateUsers != null && !this.updateUsers.isDone();
174
    }
175
 
176
    private synchronized void refreshIfNeeded(final long maxAge) {
177
        final long dataAge = System.currentTimeMillis() - this.timeOfLastFill;
178
        // refresh if the data is old enough and no update is pending
179
        if (dataAge > maxAge && !this.isUpdatePending())
180
            refresh(true);
181
    }
182
 
183
    public void refresh() {
184
        if (!this.isValid())
185
            throw new IllegalStateException("No longer valid");
186
        this.refresh(false);
187
    }
188
 
189
    // don't return the future as it might be canceled despite the fact that a subsequent
190
    // invokeLater() might need it
191
    private synchronized void refresh(final boolean thisClass) {
192
        cancelUpdate();
193
        this.updateUsers = invokeLater(new Callable<Object>() {
194
            @Override
195
            public Object call() throws Exception {
196
                // allow multiple close refresh to be cancelled by the last one
197
                if (thisClass)
198
                    Thread.sleep(30);
199
                fillUsers();
200
                return null;
201
            }
202
        }, false);
203
        // the new updateUsers is after userFuture, so we can cancel it
204
        this.userFuture = null;
205
    }
206
 
207
    public final <T> Future<T> invokeLater(final Callable<T> c) {
208
        return this.invokeLater(c, true);
209
    }
210
 
211
    public synchronized final <T> Future<T> invokeLater(final Callable<T> c, final boolean needsPreviousUpdate) {
212
        if (!this.isValid())
213
            return null;
214
        final Future<T> res = this.exec.submit(c);
215
        if (needsPreviousUpdate) {
216
            this.userFuture = res;
217
        }
218
        return res;
219
    }
220
 
221
    private void fillUsers() {
142 ilm 222
        // to keep the ORDER for #getAllUser()
223
        final Map<Integer, User> mutable = new LinkedHashMap<Integer, User>();
17 ilm 224
        final SQLRowValuesListFetcher fetcher = new SQLRowValuesListFetcher(new SQLRowValues(this.t).setAllToNull());
225
        fetcher.setOrdered(true);
226
        for (final SQLRowValues v : fetcher.fetch()) {
142 ilm 227
            final User u = new User(v);
228
            mutable.put(v.getID(), u);
17 ilm 229
        }
144 ilm 230
        final Map<Integer, User> immutable = Collections.unmodifiableMap(mutable);
231
        final Map<Integer, User> old;
232
        synchronized (this) {
233
            old = this.byID;
234
            this.byID = immutable;
235
            this.timeOfLastFill = System.currentTimeMillis();
236
        }
237
        this.supp.firePropertyChange(USERS_PROPNAME, old, immutable);
17 ilm 238
    }
239
 
144 ilm 240
    @Override
17 ilm 241
    public final SQLTable getTable() {
242
        return this.t;
243
    }
244
 
144 ilm 245
    /**
246
     * All users indexed by their IDs.
247
     *
248
     * @return all users.
249
     */
250
    public synchronized final Map<Integer, User> getUsers() {
251
        if (this.byID == null)
252
            throw new IllegalStateException(this + " wasn't started successfully");
17 ilm 253
        return this.byID;
254
    }
255
 
144 ilm 256
    public final void addUsersListener(final PropertyChangeListener l) {
257
        this.supp.addPropertyChangeListener(USERS_PROPNAME, l);
258
    }
259
 
260
    public final void removeUsersListener(final PropertyChangeListener l) {
261
        this.supp.removePropertyChangeListener(USERS_PROPNAME, l);
262
    }
263
 
142 ilm 264
    public synchronized final User getCurrentUser() {
144 ilm 265
        return this.currentUserID == null ? null : this.getUser(this.currentUserID);
17 ilm 266
    }
267
 
144 ilm 268
    public synchronized final Integer getCurrentUserID() {
269
        return this.currentUserID;
17 ilm 270
    }
271
 
144 ilm 272
    @Deprecated
273
    public void setCurrentUser(final Number id) {
274
        this.setCurrentUserID(id);
17 ilm 275
    }
276
 
144 ilm 277
    public void setCurrentUserID(final Number id) {
278
        final Integer newVal = id == null || id instanceof Integer ? (Integer) id : Integer.valueOf(id.intValue());
279
        final boolean modified;
280
        synchronized (this) {
281
            modified = !CompareUtils.equals(this.currentUserID, newVal);
282
            if (modified)
283
                this.currentUserID = newVal;
284
        }
285
        if (modified)
286
            this.supp.firePropertyChange(CURRENT_USERID_PROPNAME, null, newVal);
287
    }
288
 
289
    public final void addCurrentUserIDListener(final PropertyChangeListener l) {
290
        this.supp.addPropertyChangeListener(CURRENT_USERID_PROPNAME, l);
291
    }
292
 
293
    public final void removeCurrentUserIDListener(final PropertyChangeListener l) {
294
        this.supp.removePropertyChangeListener(CURRENT_USERID_PROPNAME, l);
295
    }
296
 
142 ilm 297
    public List<User> getAllActiveUsers() {
298
        final List<User> result = new ArrayList<User>();
299
        for (User user : this.getUsers().values()) {
132 ilm 300
            if (user.isActive()) {
301
                result.add(user);
302
            }
303
        }
304
        return result;
305
    }
306
 
142 ilm 307
    public User getUser(final Integer v) {
308
        final Map<Integer, User> users = this.getUsers();
309
        if (users.containsKey(v))
310
            return users.get(v);
17 ilm 311
        else
312
            throw new IllegalStateException("Bad user! " + v);
313
    }
314
 
315
}