OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 156 | Go to most recent revision | 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
 *
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.sql;
15
 
132 ilm 16
import org.openconcerto.sql.element.SQLElementDirectory;
156 ilm 17
import org.openconcerto.sql.element.SQLElementNamesFromXML;
132 ilm 18
import org.openconcerto.sql.model.DBRoot;
19
import org.openconcerto.sql.model.DBStructureItem;
20
import org.openconcerto.sql.model.DBSystemRoot;
21
import org.openconcerto.sql.model.FieldMapper;
22
import org.openconcerto.sql.model.HierarchyLevel;
23
import org.openconcerto.sql.model.SQLBase;
24
import org.openconcerto.sql.model.SQLDataSource;
25
import org.openconcerto.sql.model.SQLFilter;
26
import org.openconcerto.sql.model.SQLRow;
27
import org.openconcerto.sql.model.SQLServer;
28
import org.openconcerto.sql.model.SQLSystem;
29
import org.openconcerto.sql.request.SQLFieldTranslator;
144 ilm 30
import org.openconcerto.sql.users.UserManager;
132 ilm 31
import org.openconcerto.sql.users.rights.UserRightsManager;
142 ilm 32
import org.openconcerto.utils.BaseDirs;
132 ilm 33
import org.openconcerto.utils.CollectionUtils;
34
import org.openconcerto.utils.FileUtils;
35
import org.openconcerto.utils.LogUtils;
36
import org.openconcerto.utils.MultipleOutputStream;
37
import org.openconcerto.utils.NetUtils;
38
import org.openconcerto.utils.ProductInfo;
39
import org.openconcerto.utils.RTInterruptedException;
156 ilm 40
import org.openconcerto.utils.ReflectUtils;
132 ilm 41
import org.openconcerto.utils.StreamUtils;
149 ilm 42
import org.openconcerto.utils.Tuple2;
132 ilm 43
import org.openconcerto.utils.Value;
44
import org.openconcerto.utils.cc.IClosure;
144 ilm 45
import org.openconcerto.utils.cc.IPredicate;
132 ilm 46
import org.openconcerto.utils.i18n.TranslationManager;
47
 
149 ilm 48
import java.awt.Dialog.ModalityType;
49
import java.awt.event.ComponentAdapter;
50
import java.awt.event.ComponentEvent;
25 ilm 51
import java.io.BufferedOutputStream;
17 ilm 52
import java.io.ByteArrayOutputStream;
53
import java.io.File;
54
import java.io.FileInputStream;
55
import java.io.FileNotFoundException;
56
import java.io.FileOutputStream;
57
import java.io.IOException;
58
import java.io.InputStream;
25 ilm 59
import java.io.OutputStream;
17 ilm 60
import java.io.PrintStream;
61
import java.net.InetAddress;
62
import java.net.UnknownHostException;
63
import java.text.DateFormat;
64
import java.text.SimpleDateFormat;
65
import java.util.ArrayList;
66
import java.util.Arrays;
67
import java.util.Collection;
68
import java.util.Date;
69
import java.util.HashSet;
70
import java.util.List;
73 ilm 71
import java.util.ListIterator;
72
import java.util.Locale;
177 ilm 73
import java.util.Objects;
17 ilm 74
import java.util.Properties;
73 ilm 75
import java.util.ResourceBundle.Control;
17 ilm 76
import java.util.Set;
83 ilm 77
import java.util.concurrent.Callable;
78
import java.util.concurrent.ExecutionException;
79
import java.util.concurrent.Future;
80
import java.util.concurrent.FutureTask;
17 ilm 81
import java.util.logging.FileHandler;
82
import java.util.logging.Level;
83
import java.util.logging.Logger;
84
import java.util.logging.SimpleFormatter;
85
 
149 ilm 86
import javax.swing.JDialog;
87
import javax.swing.JFrame;
88
import javax.swing.JOptionPane;
89
import javax.swing.SwingUtilities;
90
 
21 ilm 91
import com.jcraft.jsch.JSch;
92
import com.jcraft.jsch.Session;
17 ilm 93
 
93 ilm 94
import net.jcip.annotations.GuardedBy;
95
import net.jcip.annotations.ThreadSafe;
96
 
17 ilm 97
/**
98
 * A configuration which takes its values primarily from Properties. You should also subclass its
99
 * different protected get*() methods. Used properties :
100
 * <dl>
101
 * <dt>server.ip</dt>
102
 * <dd>ip address of the SQL server</dd>
103
 * <dt>server.driver</dt>
104
 * <dd>the RDBMS, see {@link org.openconcerto.sql.model.SQLDataSource#DRIVERS}</dd>
105
 * <dt>server.login</dt>
106
 * <dd>the login</dd>
107
 * <dt>server.password</dt>
108
 * <dd>the password</dd>
109
 * <dt>server.base</dt>
110
 * <dd>the database (only used for systems where the root level is not SQLBase)</dd>
111
 * <dt>base.root</dt>
112
 * <dd>the name of the DBRoot</dd>
113
 * <dt>customer</dt>
114
 * <dd>used to find the default base and the mapping</dd>
115
 * <dt>JDBC_CONNECTION*</dt>
116
 * <dd>see {@link #JDBC_CONNECTION}</dd>
117
 * </dl>
118
 *
119
 * @author Sylvain CUAZ
120
 * @see #getShowAs()
121
 */
83 ilm 122
@ThreadSafe
17 ilm 123
public class PropsConfiguration extends Configuration {
124
 
125
    /**
126
     * Properties prefixed with this string will be passed to the datasource as connection
127
     * properties.
128
     */
129
    public static final String JDBC_CONNECTION = "jdbc.connection.";
130
    public static final String LOG = "log.level.";
131
    /**
132
     * If this system property is set to <code>true</code> then {@link #setupLogging(String)} will
133
     * redirect {@link System#err} and {@link System#out}.
134
     */
135
    public static final String REDIRECT_TO_FILE = "redirectToFile";
142 ilm 136
    /**
137
     * Must be one of {@link StandardStreamsDest}.
138
     */
139
    public static final String STD_STREAMS_DESTINATION = "stdStreamsDest";
17 ilm 140
 
80 ilm 141
    // properties cannot contain null, so to be able to override a default, a non-null value
142
    // meaning empty must be chosen (as setProperty(name, null) is the same as remove(name) i.e.
143
    // get the value from the default properties)
144
    public static final String EMPTY_PROP_VALUE;
145
 
17 ilm 146
    protected static enum FileMode {
147
        IN_JAR, NORMAL_FILE
148
    };
149
 
150
    // eg 2009-03/26_thursday : ordered and grouped by month
151
    private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM/dd_EEEE");
152
 
153
    public static String getHostname() {
154
        final InetAddress addr;
155
        try {
156
            addr = InetAddress.getLocalHost();
41 ilm 157
        } catch (final UnknownHostException e) {
17 ilm 158
            e.printStackTrace();
159
            return "local";
160
        }
161
        return addr.getHostName();
162
    }
163
 
41 ilm 164
    protected static Properties create(final InputStream f, final Properties defaults) throws IOException {
165
        final Properties props = new Properties(defaults);
17 ilm 166
        if (f != null) {
167
            props.load(f);
168
            f.close();
169
        }
170
        return props;
171
    }
172
 
173
    public static final Properties DEFAULTS;
93 ilm 174
 
17 ilm 175
    static {
176
        DEFAULTS = new Properties();
177
        final File wd = new File(System.getProperty("user.dir"));
178
        DEFAULTS.setProperty("wd", wd.getPath());
179
        DEFAULTS.setProperty("customer", "test");
180
        DEFAULTS.setProperty("server.ip", "127.0.0.1");
181
        DEFAULTS.setProperty("server.login", "root");
80 ilm 182
 
183
        EMPTY_PROP_VALUE = "";
184
        assert EMPTY_PROP_VALUE != null;
17 ilm 185
    }
186
 
187
    private final Properties props;
188
 
189
    // sql tree
83 ilm 190
    @GuardedBy("treeLock")
17 ilm 191
    private SQLServer server;
83 ilm 192
    @GuardedBy("treeLock")
17 ilm 193
    private DBSystemRoot sysRoot;
83 ilm 194
    @GuardedBy("treeLock")
17 ilm 195
    private DBRoot root;
83 ilm 196
    // created from root
197
    @GuardedBy("treeLock")
144 ilm 198
    private UserManager uMngr;
199
    @GuardedBy("treeLock")
73 ilm 200
    private UserRightsManager urMngr;
17 ilm 201
    // rest
83 ilm 202
    @GuardedBy("restLock")
41 ilm 203
    private ProductInfo productInfo;
83 ilm 204
    @GuardedBy("restLock")
17 ilm 205
    private SQLFilter filter;
83 ilm 206
    private final Addable<SQLElementDirectory> directory;
207
    @GuardedBy("restLock")
17 ilm 208
    private File wd;
83 ilm 209
    @GuardedBy("restLock")
142 ilm 210
    private BaseDirs baseDirs;
211
    @GuardedBy("restLock")
41 ilm 212
    private File logDir;
177 ilm 213
    @GuardedBy("restLock")
214
    private Locale locale = Locale.getDefault();
142 ilm 215
    private final boolean inIDE;
216
 
17 ilm 217
    // split sql tree and the rest since creating the tree is costly
218
    // and nodes are inter-dependant, while the rest is mostly fast
219
    // different instances, otherwise lock every Conf instances
220
    private final Object treeLock = new String("treeLock");
221
    private final Object restLock = new String("everythingElseLock");
222
 
223
    // SSL
83 ilm 224
    @GuardedBy("treeLock")
21 ilm 225
    private Session conn;
83 ilm 226
    @GuardedBy("treeLock")
149 ilm 227
    private int tunnelLocalPort = -1;
228
    @GuardedBy("treeLock")
229
    private Thread sslThread;
83 ilm 230
 
73 ilm 231
    private FieldMapper fieldMapper;
17 ilm 232
 
83 ilm 233
    @GuardedBy("treeLock")
234
    private boolean destroyed;
235
 
17 ilm 236
    public PropsConfiguration() throws IOException {
237
        this(new File("fwk_SQL.properties"), DEFAULTS);
238
    }
239
 
240
    /**
241
     * Creates a new setup.
242
     *
243
     * @param f the file from which to load.
244
     * @param defaults the defaults, can be <code>null</code>.
245
     * @throws IOException if an error occurs while reading f.
246
     */
41 ilm 247
    public PropsConfiguration(final File f, final Properties defaults) throws IOException {
17 ilm 248
        this(new FileInputStream(f), defaults);
249
    }
250
 
41 ilm 251
    public PropsConfiguration(final InputStream f, final Properties defaults) throws IOException {
17 ilm 252
        this(create(f, defaults));
253
    }
254
 
41 ilm 255
    public PropsConfiguration(final Properties props) {
17 ilm 256
        this.props = props;
83 ilm 257
        // SQLElementDirectory is thread-safe
258
        this.directory = new Addable<SQLElementDirectory>() {
259
            @Override
260
            protected SQLElementDirectory create() {
142 ilm 261
                final SQLElementDirectory res = createDirectory();
262
                res.setTranslator(createTranslator(res));
263
                return res;
83 ilm 264
            }
265
 
266
            @Override
267
            protected void add(SQLElementDirectory obj, Configuration conf) {
268
                obj.putAll(conf.getDirectory());
269
            }
144 ilm 270
 
271
            @Override
272
            protected void destroy(Future<SQLElementDirectory> future) {
273
                super.destroy(future);
274
                try {
275
                    future.get().destroy();
276
                } catch (Exception e) {
277
                    throw new IllegalStateException("Couldn't destroy the directory", e);
278
                }
279
            }
83 ilm 280
        };
142 ilm 281
        this.inIDE = Boolean.getBoolean("inIDE");
17 ilm 282
        this.setUp();
283
    }
284
 
285
    @Override
286
    public void destroy() {
83 ilm 287
        synchronized (this.treeLock) {
288
            if (this.destroyed)
289
                return;
290
            this.destroyed = true;
291
            if (this.server != null) {
292
                this.server.destroy();
293
            }
294
            closeSSLConnection();
144 ilm 295
            if (this.uMngr != null) {
296
                UserManager.getSingletonManager().clearInstanceIfSame(this.uMngr);
297
                this.uMngr.destroy();
298
            }
299
            if (this.urMngr != null) {
300
                UserRightsManager.getSingletonManager().clearInstanceIfSame(this.urMngr);
301
                this.urMngr.destroy();
302
            }
17 ilm 303
        }
83 ilm 304
 
305
        this.directory.destroy();
306
        super.destroy();
17 ilm 307
    }
308
 
83 ilm 309
    public final boolean isDestroyed() {
310
        synchronized (this.treeLock) {
311
            return this.destroyed;
312
        }
313
    }
314
 
315
    private final void checkDestroyed() {
316
        checkDestroyed(this.isDestroyed());
317
    }
318
 
319
    static private final void checkDestroyed(final boolean d) {
320
        if (d)
321
            throw new IllegalStateException("Destroyed");
322
    }
323
 
142 ilm 324
    public final boolean isInIDE() {
325
        return this.inIDE;
326
    }
327
 
41 ilm 328
    public final String getProperty(final String name) {
17 ilm 329
        return this.props.getProperty(name);
330
    }
331
 
41 ilm 332
    public final String getProperty(final String name, final String def) {
17 ilm 333
        return this.props.getProperty(name, def);
334
    }
335
 
336
    // since null aren't allowed, null means remove
41 ilm 337
    protected final void setProperty(final String name, final String val) {
17 ilm 338
        if (val == null)
339
            this.props.remove(name);
340
        else
341
            this.props.setProperty(name, val);
342
    }
343
 
41 ilm 344
    protected final void setProductInfo(final ProductInfo productInfo) {
83 ilm 345
        synchronized (this.restLock) {
346
            this.productInfo = productInfo;
347
        }
41 ilm 348
    }
349
 
17 ilm 350
    private void setUp() {
83 ilm 351
        synchronized (this.treeLock) {
352
            this.destroyed = false;
353
            this.server = null;
354
            this.sysRoot = null;
355
            this.root = null;
356
        }
357
        synchronized (this.restLock) {
358
            this.setProductInfo(ProductInfo.getInstance());
359
            this.setFilter(null);
360
        }
17 ilm 361
    }
362
 
65 ilm 363
    public final SQLSystem getSystem() {
364
        return SQLSystem.get(this.getProperty("server.driver"));
17 ilm 365
    }
366
 
367
    protected String getLogin() {
368
        return this.getProperty("server.login");
369
    }
370
 
371
    protected String getPassword() {
372
        return this.getProperty("server.password");
373
    }
374
 
375
    public String getDefaultBase() {
65 ilm 376
        final boolean rootIsBase = this.getSystem().getDBRootLevel().equals(HierarchyLevel.SQLBASE);
17 ilm 377
        return rootIsBase ? this.getRootName() : this.getSystemRootName();
378
    }
379
 
156 ilm 380
    private final String toClassName(final String rsrcName) {
381
        if (rsrcName.charAt(0) == '/')
382
            return rsrcName.substring(1).replace('/', '.');
383
        else
384
            return this.getResourceWD().getPackage().getName() + '.' + rsrcName.replace('/', '.');
385
    }
386
 
17 ilm 387
    /**
388
     * Return the correct stream depending on file mode. If file mode is
389
     * {@link FileMode#NORMAL_FILE} it will first check if a file named <code>name</code> exists,
390
     * otherwise it will look in the jar.
391
     *
392
     * @param name name of the stream, eg /ilm/f.xml.
393
     * @return the corresponding stream, or <code>null</code> if not found.
394
     */
41 ilm 395
    public final InputStream getStream(final String name) {
17 ilm 396
        final File f = getFile(name);
397
        if (mustUseClassloader(f)) {
156 ilm 398
            return getResourceWD().getResourceAsStream(name);
17 ilm 399
        } else
400
            try {
401
                return new FileInputStream(f);
41 ilm 402
            } catch (final FileNotFoundException e) {
17 ilm 403
                return null;
404
            }
405
    }
406
 
156 ilm 407
    // the "working directory" where relative names are resolved
408
    protected Class<? extends PropsConfiguration> getResourceWD() {
409
        return this.getClass();
410
    }
411
 
41 ilm 412
    private File getFile(final String name) {
17 ilm 413
        return new File(name.startsWith("/") ? name.substring(1) : name);
414
    }
415
 
416
    private boolean mustUseClassloader(final File f) {
417
        return this.getFileMode() == FileMode.IN_JAR || !f.exists();
418
    }
419
 
41 ilm 420
    public final String getResource(final String name) {
17 ilm 421
        final File f = getFile(name);
422
        if (mustUseClassloader(f)) {
156 ilm 423
            return this.getResourceWD().getResource(name).toExternalForm();
17 ilm 424
        } else {
425
            return f.getAbsolutePath();
426
        }
427
    }
428
 
429
    protected FileMode getFileMode() {
430
        return FileMode.IN_JAR;
431
    }
432
 
433
    protected final DBRoot createRoot() {
80 ilm 434
        final Value<String> rootName = getRootNameValue();
435
        if (rootName.hasValue())
436
            return this.getSystemRoot().getRoot(rootName.getValue());
17 ilm 437
        else
438
            throw new NullPointerException("no rootname");
439
    }
440
 
73 ilm 441
    // return null, if none desired
442
    protected UserRightsManager createUserRightsManager(final DBRoot root) {
144 ilm 443
        return UserRightsManager.getSingletonManager().setInstanceIfNone(root);
73 ilm 444
    }
445
 
17 ilm 446
    public String getRootName() {
80 ilm 447
        return this.getProperty("base.root", EMPTY_PROP_VALUE);
17 ilm 448
    }
449
 
80 ilm 450
    public final Value<String> getRootNameValue() {
451
        final String res = getRootName();
452
        return res == null || EMPTY_PROP_VALUE.equals(res) ? Value.<String> getNone() : Value.getSome(res);
453
    }
454
 
17 ilm 455
    protected SQLFilter createFilter() {
456
        return SQLFilter.create(this.getSystemRoot(), getDirectory());
457
    }
458
 
459
    public String getWanHostAndPort() {
460
        final String wanAddr = getProperty("server.wan.addr");
461
        final String wanPort = getProperty("server.wan.port", "22");
462
        return wanAddr + ":" + wanPort;
463
    }
464
 
67 ilm 465
    public final boolean isUsingSSH() {
83 ilm 466
        synchronized (this.treeLock) {
149 ilm 467
            return this.sslThread != null;
83 ilm 468
        }
17 ilm 469
    }
470
 
149 ilm 471
    public final int getTunnelLocalPort() {
472
        synchronized (this.treeLock) {
473
            return this.tunnelLocalPort;
474
        }
475
    }
476
 
67 ilm 477
    public final boolean hasWANProperties() {
478
        final String wanAddr = getProperty("server.wan.addr");
479
        final String wanPort = getProperty("server.wan.port");
480
        return hasWANProperties(wanAddr, wanPort);
481
    }
482
 
483
    private final boolean hasWANProperties(String wanAddr, String wanPort) {
484
        return wanAddr != null && wanPort != null;
485
    }
486
 
17 ilm 487
    protected SQLServer createServer() {
488
        final String wanAddr = getProperty("server.wan.addr");
489
        final String wanPort = getProperty("server.wan.port");
67 ilm 490
        if (!hasWANProperties(wanAddr, wanPort))
17 ilm 491
            return doCreateServer();
492
 
149 ilm 493
        final Tuple2<String, Integer> serverAndPort = parseServerAddressAndPort();
494
        final String serverAndPortString = serverAndPort.get0() + ":" + serverAndPort.get1();
142 ilm 495
 
67 ilm 496
        // if wanAddr is specified, always include it in ID, that way if we connect through the LAN
497
        // or through the WAN we have the same ID
149 ilm 498
        final String serverID = "tunnel to " + wanAddr + ":" + wanPort + " then " + serverAndPortString;
17 ilm 499
        final Logger log = Log.get();
500
        Exception origExn = null;
501
        if (!"true".equals(getProperty("server.wan.only"))) {
502
            try {
149 ilm 503
                final SQLServer defaultServer = doCreateServer(serverAndPortString, null, serverID);
17 ilm 504
                // works since all ds params are provided by doCreateServer()
505
                defaultServer.getSystemRoot(getSystemRootName());
506
                // ok
507
                log.config("using " + defaultServer);
508
 
509
                return defaultServer;
41 ilm 510
            } catch (final RuntimeException e) {
17 ilm 511
                origExn = e;
512
                // on essaye par SSL
513
                log.config(e.getLocalizedMessage());
514
            }
515
            assert origExn != null;
516
        }
149 ilm 517
        final SQLServer serverThruSSL;
17 ilm 518
        try {
149 ilm 519
            log.info("Connecting with SSL to " + wanAddr + ":" + wanPort);
520
            this.openSSLConnection(wanAddr, Integer.valueOf(wanPort), serverAndPort.get0(), serverAndPort.get1());
521
            serverThruSSL = doCreateServer("localhost:" + this.tunnelLocalPort, null, serverID);
17 ilm 522
            serverThruSSL.getSystemRoot(getSystemRootName());
41 ilm 523
        } catch (final Exception e) {
149 ilm 524
            // even if the tunnel was set up successfully, close it if the SQLServer couldn't be
525
            // created, that way the next time createServer() is called, we can start again the
526
            // whole process (e.g. checking properties, retrying non-WAN server) without having to
527
            // worry about a lingering tunnel.
17 ilm 528
            this.closeSSLConnection();
149 ilm 529
            // no datasource will remain that uses the port, so forget about it and find a new one
530
            // the next time
531
            this.tunnelLocalPort = -1;
532
            final IllegalStateException exn = new IllegalStateException("Couldn't connect to the DB through SSL", e);
533
            if (origExn != null)
534
                exn.addSuppressed(origExn);
535
            throw exn;
17 ilm 536
        }
537
        return serverThruSSL;
538
 
539
    }
540
 
149 ilm 541
    // TODO add and use server.port
542
    public final Tuple2<String, Integer> parseServerAddressAndPort() {
543
        final String serverPropVal = getProperty("server.ip");
544
        final List<String> serverAndPort = Arrays.asList(serverPropVal.split(":"));
545
        if (serverAndPort.size() != 2)
546
            throw new IllegalStateException("Not in 'host:port' format : " + serverPropVal);
547
        return Tuple2.create(serverAndPort.get(0), Integer.valueOf(serverAndPort.get(1)));
548
    }
549
 
550
    protected final void reconnectSSL() throws Exception {
551
        synchronized (this.treeLock) {
552
            // already destroyed or still OK
553
            if (this.conn == null || this.conn.isConnected())
554
                return;
555
 
556
            Log.get().log(Level.WARNING, "SSL disconnected, trying to reconnect");
557
 
558
            final String wanAddr = getProperty("server.wan.addr");
559
            final String wanPort = getProperty("server.wan.port");
560
            final Tuple2<String, Integer> serverAndPort = parseServerAddressAndPort();
561
 
562
            try {
563
                // cannot just call connect() again, as Session is one-time use
564
                // http://flyingjxswithjava.blogspot.fr/2015/03/comjcraftjschjschexception-packet.html
565
                this.openSSLConnection(wanAddr, Integer.valueOf(wanPort), serverAndPort.get0(), serverAndPort.get1());
566
                Log.get().log(Level.WARNING, "SSL successfully reconnected to " + this.conn.getHost() + ":" + this.conn.getPort());
567
            } catch (Exception e) {
568
                // don't leave an SSL connection without a tunnel
569
                this.conn.disconnect();
570
                throw e;
571
            }
572
        }
573
    }
574
 
17 ilm 575
    private SQLServer doCreateServer() {
67 ilm 576
        return doCreateServer(null);
577
    }
578
 
579
    private SQLServer doCreateServer(final String id) {
580
        return doCreateServer(this.getProperty("server.ip"), null, id);
581
    }
582
 
583
    private SQLServer doCreateServer(final String host, final String port, final String id) {
17 ilm 584
        // give login/password as its often the case that they are the same for all the bases of a
585
        // server (mandated for MySQL : when the graph is built, it needs access to all the bases)
67 ilm 586
        final SQLServer res = new SQLServer(getSystem(), host, port, getLogin(), getPassword(), new IClosure<DBSystemRoot>() {
17 ilm 587
            @Override
41 ilm 588
            public void executeChecked(final DBSystemRoot input) {
65 ilm 589
                input.setRootsToMap(getRootsToMap());
63 ilm 590
                initSystemRoot(input);
17 ilm 591
            }
592
        }, new IClosure<SQLDataSource>() {
593
            @Override
41 ilm 594
            public void executeChecked(final SQLDataSource input) {
17 ilm 595
                initDS(input);
596
            }
597
        });
67 ilm 598
        if (id != null)
599
            res.setID(id);
600
        return res;
17 ilm 601
    }
602
 
149 ilm 603
    private void openSSLConnection(final String addr, final int port, final String tunnelRemoteHost, final int tunnelRemotePort) {
83 ilm 604
        checkDestroyed();
17 ilm 605
        final String username = getSSLUserName();
41 ilm 606
        final String pass = getSSLPassword();
149 ilm 607
        final boolean reconnect = this.tunnelLocalPort > 0;
17 ilm 608
        boolean isAuthenticated = false;
21 ilm 609
 
610
        final JSch jsch = new JSch();
17 ilm 611
        try {
41 ilm 612
            if (pass == null) {
613
                final ByteArrayOutputStream out = new ByteArrayOutputStream(700);
614
                final String name = username + "_dsa";
615
                final InputStream in = getClass().getResourceAsStream(name);
616
                if (in == null)
617
                    throw new IllegalStateException("Missing private key " + getClass().getCanonicalName() + "/" + name);
618
                StreamUtils.copy(in, out);
619
                in.close();
620
                jsch.addIdentity(username, out.toByteArray(), null, null);
621
            }
21 ilm 622
 
623
            this.conn = jsch.getSession(username, addr, port);
41 ilm 624
            if (pass != null)
625
                this.conn.setPassword(pass);
21 ilm 626
            final Properties config = new Properties();
627
            // Set StrictHostKeyChecking property to no to avoid UnknownHostKey issue
628
            config.put("StrictHostKeyChecking", "no");
629
            // *2 gain
630
            config.put("compression.s2c", "zlib@openssh.com,zlib,none");
631
            config.put("compression.c2s", "zlib@openssh.com,zlib,none");
632
            this.conn.setConfig(config);
633
            // wait no more than 6 seconds for TCP connection
149 ilm 634
            this.conn.setTimeout(6000);
635
            // ATTN for now this just calls setTimeout()
636
            this.conn.setServerAliveInterval(6000);
637
            this.conn.setServerAliveCountMax(1);
638
            this.conn.connect();
639
 
73 ilm 640
            afterSSLConnect(this.conn);
21 ilm 641
 
642
            isAuthenticated = true;
149 ilm 643
 
644
            // keep same local port so that we don't have to change the datasource
645
            final int localPort = reconnect ? this.tunnelLocalPort : NetUtils.findFreePort(5436);
646
            try {
647
                Log.get().info("Creating SSL tunnel from local port " + localPort + " to remote " + tunnelRemoteHost + ":" + tunnelRemotePort);
648
                this.conn.setPortForwardingL(localPort, tunnelRemoteHost, tunnelRemotePort);
649
            } catch (final Exception e1) {
650
                throw new IllegalStateException("Impossible de créer le tunnel sécurisé", e1);
651
            }
652
            assert reconnect == (this.sslThread != null);
653
            if (!reconnect) {
654
                this.tunnelLocalPort = localPort;
655
                // With ServerAliveInterval & ServerAliveCount, the connection should disconnect
656
                // itself in case of network failure, so we try to reconnect it
657
                final Thread t = new Thread(new Runnable() {
658
                    @Override
659
                    public void run() {
660
                        while (true) {
661
                            try {
662
                                Thread.sleep(2000);
663
                                try {
664
                                    reconnectSSL();
665
                                } catch (Exception e) {
666
                                    // re-try later
667
                                    Log.get().log(Level.WARNING, "Error while checking SSL connection", e);
668
                                }
669
                            } catch (InterruptedException e) {
670
                                // used by destroy()
671
                                break;
672
                            }
673
                        }
674
                    }
675
                });
676
                t.setDaemon(true);
677
                t.setName("SSL connection watcher");
678
                t.start();
679
                this.sslThread = t;
680
            }
681
            assert this.sslThread != null;
41 ilm 682
        } catch (final Exception e) {
17 ilm 683
            throw new IllegalStateException("Connection failed", e);
684
        }
685
        if (!isAuthenticated)
686
            throw new IllegalStateException("Authentication failed.");
687
    }
688
 
73 ilm 689
    protected void afterSSLConnect(Session conn) {
690
    }
691
 
17 ilm 692
    public String getSSLUserName() {
693
        return this.getProperty("server.wan.user");
694
    }
695
 
41 ilm 696
    protected String getSSLPassword() {
697
        return this.getProperty("server.wan.password");
698
    }
699
 
17 ilm 700
    private void closeSSLConnection() {
83 ilm 701
        synchronized (this.treeLock) {
149 ilm 702
            if (this.sslThread != null) {
703
                this.sslThread.interrupt();
704
                this.sslThread = null;
705
            }
83 ilm 706
            if (this.conn != null) {
707
                this.conn.disconnect();
708
                this.conn = null;
709
            }
17 ilm 710
        }
711
    }
712
 
67 ilm 713
    // the result can be modified (avoid that each subclass recreates an instance)
83 ilm 714
    // but it can be null (meaning map all)
17 ilm 715
    protected Collection<String> getRootsToMap() {
83 ilm 716
        final String rootsToMap = getProperty("systemRoot.rootsToMap");
717
        if ("*".equals(rootsToMap))
718
            return null;
719
 
17 ilm 720
        final Set<String> res = new HashSet<String>();
721
 
80 ilm 722
        final Value<String> rootName = getRootNameValue();
723
        if (rootName.hasValue())
724
            res.add(rootName.getValue());
17 ilm 725
        if (rootsToMap != null)
726
            res.addAll(SQLRow.toList(rootsToMap));
727
 
728
        return res;
729
    }
730
 
67 ilm 731
    // the result can be modified (avoid that each subclass recreates an instance)
732
    protected List<String> getRootPath() {
733
        return new ArrayList<String>(SQLRow.toList(getProperty("systemRoot.rootPath", "")));
734
    }
735
 
17 ilm 736
    public String getSystemRootName() {
737
        return this.getProperty("systemRoot");
738
    }
739
 
740
    protected DBSystemRoot createSystemRoot() {
741
        // all ds params specified by createServer()
742
        final DBSystemRoot res = this.getServer(false).getSystemRoot(this.getSystemRootName());
83 ilm 743
        setupSystemRoot(res, true);
744
        return res;
745
    }
746
 
747
    // to be called after having a data source
748
    protected final void setupSystemRoot(final DBSystemRoot res) {
749
        this.setupSystemRoot(res, false);
750
    }
751
 
752
    private void setupSystemRoot(final DBSystemRoot res, final boolean brandNew) {
753
        if (!brandNew)
754
            res.unsetRootPath();
17 ilm 755
        // handle case when the root is not yet created
756
        if (res.getChildrenNames().contains(this.getRootName()))
757
            res.setDefaultRoot(this.getRootName());
67 ilm 758
        for (final String root : getRootPath()) {
759
            // not all the items of the path may exist in every databases (eg Controle.Common)
760
            if (res.getChildrenNames().contains(root))
761
                res.appendToRootPath(root);
17 ilm 762
        }
763
    }
764
 
63 ilm 765
    // called at the end of the DBSystemRoot constructor (before having a data source)
766
    protected void initSystemRoot(DBSystemRoot input) {
767
    }
768
 
17 ilm 769
    protected void initDS(final SQLDataSource ds) {
770
        ds.setCacheEnabled(true);
67 ilm 771
        // supported by postgreSQL from 9.1-901, see also Connection#setClientInfo
83 ilm 772
        // also supported by MS SQL
73 ilm 773
        final String appID = getAppID();
774
        if (appID != null)
775
            ds.addConnectionProperty("ApplicationName", appID);
17 ilm 776
        propIterate(new IClosure<String>() {
41 ilm 777
            @Override
778
            public void executeChecked(final String propName) {
17 ilm 779
                final String jdbcName = propName.substring(JDBC_CONNECTION.length());
780
                ds.addConnectionProperty(jdbcName, PropsConfiguration.this.getProperty(propName));
781
            }
782
        }, JDBC_CONNECTION);
783
    }
784
 
785
    public final void propIterate(final IClosure<String> cl, final String startsWith) {
144 ilm 786
        this.propIterate(cl, new IPredicate<String>() {
41 ilm 787
            @Override
144 ilm 788
            public boolean evaluateChecked(final String propName) {
789
                return propName.startsWith(startsWith);
17 ilm 790
            }
791
        });
792
    }
793
 
794
    /**
795
     * Apply <code>cl</code> for each property that matches <code>filter</code>.
796
     *
797
     * @param cl what to do for each found property.
798
     * @param filter which property to use.
799
     */
144 ilm 800
    public final void propIterate(final IClosure<String> cl, final IPredicate<? super String> filter) {
41 ilm 801
        for (final String propName : this.props.stringPropertyNames()) {
144 ilm 802
            if (filter.evaluateChecked(propName)) {
17 ilm 803
                cl.executeChecked(propName);
804
            }
805
        }
806
    }
807
 
151 ilm 808
    public final Set<String> getPropertyNames() {
809
        return this.props.stringPropertyNames();
810
    }
811
 
17 ilm 812
    /**
813
     * For each property starting with {@link #LOG}, set the level of the specified logger to the
814
     * property's value. Eg if there's "log.level.=FINE", the root logger will be set to log FINE
815
     * messages.
816
     */
817
    public final void setLoggersLevel() {
818
        this.propIterate(new IClosure<String>() {
41 ilm 819
            @Override
820
            public void executeChecked(final String propName) {
17 ilm 821
                final String logName = propName.substring(LOG.length());
822
                LogUtils.getLogger(logName).setLevel(Level.parse(getProperty(propName)));
823
            }
824
        }, LOG);
825
    }
826
 
827
    public void setupLogging() {
828
        this.setupLogging("logs");
829
    }
830
 
142 ilm 831
    public final void setupLogging(final String dirName) {
832
        this.setupLogging(dirName, getStandardStreamsDestination());
17 ilm 833
    }
834
 
142 ilm 835
    protected final StandardStreamsDest getStandardStreamsDestination() {
836
        String propVal = System.getProperty(STD_STREAMS_DESTINATION);
837
        if (propVal == null)
838
            propVal = this.getProperty(STD_STREAMS_DESTINATION);
839
 
840
        final StandardStreamsDest res;
841
        if (propVal != null)
842
            res = StandardStreamsDest.valueOf(propVal);
843
        else
844
            res = getStandardStreamsDestinationDefault();
845
        return res;
25 ilm 846
    }
847
 
142 ilm 848
    // used if neither system property nor configuration property are set
849
    protected StandardStreamsDest getStandardStreamsDestinationDefault() {
850
        return Boolean.getBoolean(REDIRECT_TO_FILE) ? StandardStreamsDest.ALSO_TO_FILE : StandardStreamsDest.DEFAULT;
851
    }
852
 
28 ilm 853
    protected DateFormat getLogDateFormat() {
854
        return DATE_FORMAT;
855
    }
856
 
83 ilm 857
    private final File getValidLogDir(final String dirName) {
17 ilm 858
        final File logDir;
859
        try {
860
            final File softLogDir = new File(this.getWD() + "/" + dirName + "/" + getHostname() + "-" + System.getProperty("user.name"));
41 ilm 861
            // don't throw an exception if this fails, we'll fall back to homeLogDir
17 ilm 862
            softLogDir.mkdirs();
41 ilm 863
            if (softLogDir.canWrite()) {
17 ilm 864
                logDir = softLogDir;
41 ilm 865
            } else {
17 ilm 866
                final File homeLogDir = new File(System.getProperty("user.home") + "/." + this.getAppName() + "/" + dirName);
867
                FileUtils.mkdir_p(homeLogDir);
41 ilm 868
                if (homeLogDir.canWrite())
869
                    logDir = homeLogDir;
870
                else
871
                    throw new IOException("Home log directory not writeable : " + homeLogDir);
17 ilm 872
            }
41 ilm 873
            assert logDir.exists() && logDir.canWrite();
19 ilm 874
            System.out.println("Log directory: " + logDir.getAbsolutePath());
41 ilm 875
        } catch (final IOException e) {
17 ilm 876
            throw new IllegalStateException("unable to create log dir", e);
877
        }
83 ilm 878
        return logDir;
879
    }
880
 
142 ilm 881
    static public enum StandardStreamsDest {
882
        DEFAULT(true), ONLY_TO_FILE(false), ALSO_TO_FILE(true);
883
        private final boolean hasDefaultStreams;
884
 
885
        private StandardStreamsDest(boolean hasDefaultStreams) {
886
            this.hasDefaultStreams = hasDefaultStreams;
887
        }
888
 
889
        public final boolean hasDefaultStreams() {
890
            return this.hasDefaultStreams;
891
        }
892
    }
893
 
894
    public final void setupLogging(final String dirName, final StandardStreamsDest stdRedirect) {
83 ilm 895
        final File logDir;
896
        synchronized (this.restLock) {
897
            if (this.logDir != null)
898
                throw new IllegalStateException("Already set to " + this.logDir);
899
            logDir = getValidLogDir(dirName);
900
            this.logDir = logDir;
901
        }
28 ilm 902
        final String logNameBase = this.getAppName() + "_" + getLogDateFormat().format(new Date());
17 ilm 903
 
41 ilm 904
        // must be done before setUpConsoleHandler(), otherwise log output not redirected
142 ilm 905
        if (stdRedirect != StandardStreamsDest.DEFAULT) {
17 ilm 906
            final File logFile = new File(logDir, (logNameBase + ".txt"));
907
            try {
41 ilm 908
                FileUtils.mkdir_p(logFile.getParentFile());
142 ilm 909
                System.out.println("Standard output and error file: " + logFile.getAbsolutePath());
25 ilm 910
                final OutputStream fileOut = new FileOutputStream(logFile, true);
911
                final OutputStream out, err;
142 ilm 912
                if (this.isInIDE() || stdRedirect != StandardStreamsDest.ONLY_TO_FILE) {
61 ilm 913
                    System.out.println("Redirecting standard output to file and console");
142 ilm 914
                    out = new MultipleOutputStream(fileOut, System.out);
61 ilm 915
                    System.out.println("Redirecting error output to file and console");
142 ilm 916
                    err = new MultipleOutputStream(fileOut, System.err);
25 ilm 917
                } else {
142 ilm 918
                    System.out.println("Redirecting standard output to file");
25 ilm 919
                    out = fileOut;
142 ilm 920
                    System.out.println("Redirecting error output to file");
25 ilm 921
                    err = fileOut;
922
                }
923
                System.setErr(new PrintStream(new BufferedOutputStream(err, 128), true));
924
                System.setOut(new PrintStream(new BufferedOutputStream(out, 128), true));
73 ilm 925
                // Takes about 350ms so run it async
926
                new Thread(new Runnable() {
927
                    @Override
928
                    public void run() {
929
                        try {
930
                            FileUtils.ln(logFile, new File(logDir, "last.log"));
931
                        } catch (final IOException e) {
932
                            // the link is not important
933
                            e.printStackTrace();
934
                        }
935
                    }
936
                }).start();
937
            } catch (final Exception e) {
93 ilm 938
                throw new IllegalStateException("Redirection des sorties standards impossible", e);
17 ilm 939
            }
19 ilm 940
        } else {
41 ilm 941
            System.out.println("Standard streams not redirected to file");
17 ilm 942
        }
943
 
944
        // removes default
945
        LogUtils.rmRootHandlers();
28 ilm 946
        // add console handler
17 ilm 947
        LogUtils.setUpConsoleHandler();
28 ilm 948
        // add file handler (supports concurrent launches, doesn't depend on date)
17 ilm 949
        try {
950
            final File logFile = new File(logDir, this.getAppName() + "-%u-age%g.log");
41 ilm 951
            FileUtils.mkdir_p(logFile.getParentFile());
19 ilm 952
            System.out.println("Logger logs: " + logFile.getAbsolutePath());
17 ilm 953
            // 2 files of at most 5M, each new launch append
954
            // if multiple concurrent launches %u is used
955
            final FileHandler fh = new FileHandler(logFile.getPath(), 5 * 1024 * 1024, 2, true);
956
            fh.setFormatter(new SimpleFormatter());
957
            Logger.getLogger("").addHandler(fh);
73 ilm 958
        } catch (final Exception e) {
93 ilm 959
            throw new IllegalStateException("Enregistrement du Logger désactivé", e);
17 ilm 960
        }
961
 
962
        this.setLoggersLevel();
963
    }
964
 
41 ilm 965
    public final File getLogDir() {
83 ilm 966
        synchronized (this.restLock) {
967
            return this.logDir;
968
        }
41 ilm 969
    }
970
 
17 ilm 971
    public void tearDownLogging() {
972
        this.tearDownLogging(Boolean.getBoolean(REDIRECT_TO_FILE));
973
    }
974
 
975
    public void tearDownLogging(final boolean redirectToFile) {
976
        LogUtils.rmRootHandlers();
977
        if (redirectToFile) {
978
            System.out.close();
979
            System.err.close();
980
        }
981
    }
982
 
983
    protected SQLElementDirectory createDirectory() {
984
        return new SQLElementDirectory();
985
    }
986
 
156 ilm 987
    // Use resource name to be able to use absolute (beginning with /) or relative path (to this
988
    // class)
17 ilm 989
    protected List<String> getMappings() {
73 ilm 990
        return Arrays.asList("mapping", "mapping-" + this.getProperty("customer"));
17 ilm 991
    }
992
 
142 ilm 993
    protected SQLFieldTranslator createTranslator(final SQLElementDirectory dir) {
17 ilm 994
        final List<String> mappings = getMappings();
995
        if (mappings.size() == 0)
996
            throw new IllegalStateException("empty mappings");
997
 
156 ilm 998
        final SQLFieldTranslator trns = new SQLFieldTranslator(this.getRoot(), dir);
73 ilm 999
        // perhaps listen to UserProps (as in TM)
1000
        return loadTranslations(trns, this.getRoot(), mappings);
1001
    }
1002
 
1003
    protected final SQLFieldTranslator loadTranslations(final SQLFieldTranslator trns, final DBRoot root, final List<String> mappings) {
177 ilm 1004
        final Locale locale = this.getLocale();
73 ilm 1005
        final Control cntrl = TranslationManager.getControl();
1006
        boolean found = false;
1007
        // better to have a translation in the correct language than a translation for the correct
1008
        // customer in the wrong language
1009
        final String fakeBaseName = "";
1010
        for (Locale targetLocale = locale; targetLocale != null && !found; targetLocale = cntrl.getFallbackLocale(fakeBaseName, targetLocale)) {
1011
            final List<Locale> langs = cntrl.getCandidateLocales(fakeBaseName, targetLocale);
1012
            // SQLFieldTranslator overwrite, so we need to load from general to specific
1013
            final ListIterator<Locale> listIterator = CollectionUtils.getListIterator(langs, true);
1014
            while (listIterator.hasNext()) {
1015
                final Locale lang = listIterator.next();
156 ilm 1016
                final SQLElementNamesFromXML elemNames = new SQLElementNamesFromXML(lang);
1017
                found |= loadTranslations(trns, PropsConfiguration.class.getResourceAsStream(cntrl.toBundleName("mapping", lang) + ".xml"), root, elemNames);
73 ilm 1018
                for (final String m : mappings) {
156 ilm 1019
                    final String bundleName = cntrl.toBundleName(m, lang);
1020
                    found |= loadTranslations(trns, this.getStream(bundleName + ".xml"), root, elemNames);
1021
                    final Class<? extends TranslatorFiller> loadedClass = ReflectUtils.getSubclass(toClassName(bundleName), TranslatorFiller.class);
1022
                    if (loadedClass != null) {
1023
                        try {
1024
                            ReflectUtils.createInstance(loadedClass, this).fill(trns);
1025
                        } catch (Exception e) {
1026
                            Log.get().log(Level.WARNING, "Couldn't use " + loadedClass, e);
1027
                        }
1028
                    }
73 ilm 1029
                }
1030
            }
17 ilm 1031
        }
1032
        return trns;
1033
    }
1034
 
156 ilm 1035
    @FunctionalInterface
1036
    static public interface TranslatorFiller {
1037
        void fill(final SQLFieldTranslator t);
1038
    }
1039
 
1040
    static public abstract class AbstractTranslatorFiller implements TranslatorFiller {
1041
        private final PropsConfiguration conf;
1042
 
1043
        public AbstractTranslatorFiller(final PropsConfiguration conf) {
1044
            this.conf = conf;
1045
        }
1046
 
1047
        protected final PropsConfiguration getConf() {
1048
            return this.conf;
1049
        }
1050
    }
1051
 
1052
    private final boolean loadTranslations(final SQLFieldTranslator trns, final InputStream in, final DBRoot root, final SQLElementNamesFromXML elemNames) {
73 ilm 1053
        final boolean res = in != null;
1054
        // do not force to have one mapping for each client and each locale
1055
        if (res)
156 ilm 1056
            trns.load(root, in, elemNames);
73 ilm 1057
        return res;
1058
    }
1059
 
17 ilm 1060
    protected File createWD() {
1061
        return new File(this.getProperty("wd"));
1062
    }
1063
 
142 ilm 1064
    protected BaseDirs createBaseDirs() {
1065
        return BaseDirs.create(getProductInfo(), getAppVariant());
1066
    }
1067
 
17 ilm 1068
    // *** add
1069
 
1070
    /**
1071
     * Add the passed Configuration to this. If an item is not already created, this method won't,
1072
     * instead the item to add will be stored. Also items of this won't be replaced by those of
1073
     * <code>conf</code>.
1074
     *
1075
     * @param conf the conf to add.
1076
     */
41 ilm 1077
    @Override
1078
    public final Configuration add(final Configuration conf) {
83 ilm 1079
        this.directory.add(conf);
1080
        return this;
1081
    }
17 ilm 1082
 
83 ilm 1083
    private abstract class Addable<T> {
17 ilm 1084
 
83 ilm 1085
        @GuardedBy("this")
1086
        private boolean destroyed;
1087
        @GuardedBy("this")
1088
        private final List<Configuration> toAdd;
1089
        @GuardedBy("this")
1090
        private Future<T> f;
17 ilm 1091
 
83 ilm 1092
        protected Addable() {
1093
            super();
1094
            synchronized (this) {
1095
                this.toAdd = new ArrayList<Configuration>();
1096
                this.f = null;
1097
                this.destroyed = false;
1098
            }
1099
        }
1100
 
1101
        public final void add(final Configuration conf) {
1102
            final boolean computeStarted;
1103
            synchronized (this) {
1104
                computeStarted = isComputeStarted();
1105
                if (!computeStarted)
1106
                    this.toAdd.add(conf);
1107
            }
1108
            if (computeStarted) {
1109
                // T must be thread-safe
1110
                add(this.get(), conf);
1111
            }
1112
        }
1113
 
1114
        // synchronize on this (and not some private lock) to allow callers to do something before
1115
        // the result changes
1116
        protected final boolean isComputeStarted() {
1117
            synchronized (this) {
1118
                return this.f != null;
1119
            }
1120
        }
1121
 
1122
        public final T get() {
1123
            // result
1124
            final Future<T> future;
1125
            // to run
1126
            final FutureTask<T> futureTask;
1127
            synchronized (this) {
1128
                checkDestroyed(this.destroyed);
1129
                if (this.f == null) {
1130
                    final List<Configuration> l = new ArrayList<Configuration>(this.toAdd);
1131
                    this.toAdd.clear();
1132
                    futureTask = new FutureTask<T>(new Callable<T>() {
1133
                        @Override
1134
                        public T call() throws Exception {
1135
                            final T res = create();
1136
                            // don't call alien code with lock
1137
                            assert !Thread.holdsLock(Addable.this);
1138
                            for (final Configuration s : l) {
1139
                                // deadlock if get() is called (will hang on future.get())
1140
                                add(res, s);
1141
                            }
1142
                            return res;
1143
                        }
1144
                    });
1145
                    this.f = futureTask;
1146
                    future = futureTask;
1147
                } else {
1148
                    futureTask = null;
1149
                    future = this.f;
1150
                }
1151
            }
1152
            if (futureTask != null)
1153
                futureTask.run();
1154
            try {
1155
                return future.get();
1156
            } catch (InterruptedException e) {
1157
                throw new RTInterruptedException(e);
1158
            } catch (ExecutionException e) {
1159
                throw new IllegalStateException(e);
1160
            }
1161
        }
1162
 
1163
        protected abstract T create();
1164
 
1165
        protected abstract void add(final T obj, final Configuration conf);
1166
 
1167
        public final void destroy() {
1168
            final Future<T> future;
1169
            synchronized (this) {
1170
                this.destroyed = true;
1171
                future = this.f;
1172
            }
1173
            if (future != null)
1174
                destroy(future);
1175
        }
1176
 
1177
        // nothing by default
1178
        protected void destroy(final Future<T> future) {
1179
        }
17 ilm 1180
    }
1181
 
1182
    // *** getters
1183
 
41 ilm 1184
    @Override
17 ilm 1185
    public final ShowAs getShowAs() {
132 ilm 1186
        return this.getDirectory().getShowAs();
17 ilm 1187
    }
1188
 
41 ilm 1189
    @Override
17 ilm 1190
    public final SQLBase getBase() {
1191
        return this.getNode(SQLBase.class);
1192
    }
1193
 
41 ilm 1194
    @Override
17 ilm 1195
    public final DBRoot getRoot() {
1196
        synchronized (this.treeLock) {
83 ilm 1197
            checkDestroyed();
17 ilm 1198
            if (this.root == null)
1199
                this.setRoot(this.createRoot());
83 ilm 1200
            return this.root;
17 ilm 1201
        }
1202
    }
1203
 
144 ilm 1204
    public final UserManager getUserManager() {
1205
        synchronized (this.treeLock) {
1206
            getRoot();
1207
            return this.uMngr;
1208
        }
1209
    }
1210
 
1211
    public final UserRightsManager getUserRightsManager() {
1212
        synchronized (this.treeLock) {
1213
            getRoot();
1214
            return this.urMngr;
1215
        }
1216
    }
1217
 
17 ilm 1218
    @Override
1219
    public final DBSystemRoot getSystemRoot() {
1220
        synchronized (this.treeLock) {
83 ilm 1221
            checkDestroyed();
17 ilm 1222
            if (this.sysRoot == null)
1223
                this.sysRoot = this.createSystemRoot();
83 ilm 1224
            return this.sysRoot;
17 ilm 1225
        }
1226
    }
1227
 
149 ilm 1228
    public final Thread createDBCheckThread(final JFrame mainFrame, final Runnable quitRunnable) {
1229
        final DBSystemRoot sysRoot = this.getSystemRoot();
1230
        final String quit = "Quitter le logiciel";
1231
        final JOptionPane optionPane = new JOptionPane("Impossible de contacter la base. Cette fenêtre se fermera dès le rétablissement de la connexion. Sinon vous pouvez quitter le logiciel.",
1232
                JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE, null, new Object[] { quit }, null);
1233
        final JDialog dialog = optionPane.createDialog(mainFrame, "Erreur de connexion");
1234
        dialog.setModalityType(ModalityType.APPLICATION_MODAL);
1235
        // can only be closed by us (if connection is restored) or by choosing quit
1236
        dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
1237
        dialog.addComponentListener(new ComponentAdapter() {
1238
            @Override
1239
            public void componentHidden(ComponentEvent e) {
1240
                if (optionPane.getValue().equals(quit))
1241
                    quitRunnable.run();
1242
                else
1243
                    // only the thread can change the dialog, otherwise pb would be incoherent
1244
                    dialog.setVisible(true);
1245
            }
1246
        });
1247
        dialog.pack();
1248
        final Thread dbConn = new Thread(new Runnable() {
1249
            @Override
1250
            public void run() {
1251
                boolean pb = false;
1252
                while (true) {
1253
                    try {
1254
                        Thread.sleep((pb ? 4 : 20) * 1000);
1255
                    } catch (InterruptedException e1) {
1256
                        // ignore
1257
                        e1.printStackTrace();
1258
                    }
1259
                    try {
1260
                        sysRoot.getDataSource().validateDBConnectivity();
1261
                        if (pb) {
1262
                            SwingUtilities.invokeLater(new Runnable() {
1263
                                @Override
1264
                                public void run() {
1265
                                    // don't use setVisible(false) otherwise
1266
                                    // componentHidden() will be called
1267
                                    dialog.dispose();
1268
                                }
1269
                            });
1270
                            pb = false;
1271
                        }
1272
                    } catch (Exception e) {
1273
                        if (!pb) {
1274
                            pb = true;
1275
                            e.printStackTrace();
1276
                            SwingUtilities.invokeLater(new Runnable() {
1277
                                @Override
1278
                                public void run() {
1279
                                    dialog.setLocationRelativeTo(dialog.getOwner());
1280
                                    dialog.setVisible(true);
1281
                                }
1282
                            });
1283
                        }
1284
                    }
1285
                }
1286
            }
1287
        }, "databaseConnectivity");
1288
        dbConn.setDaemon(true);
1289
        return dbConn;
1290
    }
1291
 
17 ilm 1292
    /**
1293
     * Get the node of the asked class, creating just the necessary instances (ie getNode(Server)
1294
     * won't do a getBase().getServer()).
1295
     *
1296
     * @param <T> the type wanted.
1297
     * @param clazz the class wanted, eg SQLBase.class, DBSystemRoot.class.
1298
     * @return the corresponding instance, eg getBase() for SQLBase, getServer() or getBase() for
1299
     *         DBSystemRoot depending on the SQL system.
1300
     */
41 ilm 1301
    public final <T extends DBStructureItem<?>> T getNode(final Class<T> clazz) {
17 ilm 1302
        final SQLSystem sys = this.getServer().getSQLSystem();
1303
        final HierarchyLevel l = sys.getLevel(clazz);
1304
        if (l == HierarchyLevel.SQLSERVER)
1305
            return this.getServer().getAnc(clazz);
1306
        else if (l == sys.getLevel(DBSystemRoot.class))
1307
            return this.getSystemRoot().getAnc(clazz);
1308
        else if (l == sys.getLevel(DBRoot.class))
1309
            return this.getRoot().getAnc(clazz);
1310
        else
1311
            throw new IllegalArgumentException("doesn't know an item of " + clazz);
1312
    }
1313
 
1314
    public final SQLServer getServer() {
1315
        return this.getServer(true);
1316
    }
1317
 
1318
    private final SQLServer getServer(final boolean initSysRoot) {
1319
        synchronized (this.treeLock) {
83 ilm 1320
            checkDestroyed();
17 ilm 1321
            if (this.server == null) {
1322
                this.setServer(this.createServer());
1323
                // necessary otherwise the returned server has no datasource
1324
                // (eg getChildren() will fail)
1325
                if (initSysRoot && this.server.getSQLSystem().getLevel(DBSystemRoot.class) == HierarchyLevel.SQLSERVER)
1326
                    this.getSystemRoot();
1327
            }
83 ilm 1328
            return this.server;
17 ilm 1329
        }
1330
    }
1331
 
41 ilm 1332
    @Override
17 ilm 1333
    public final SQLFilter getFilter() {
1334
        synchronized (this.restLock) {
1335
            if (this.filter == null)
1336
                this.setFilter(this.createFilter());
83 ilm 1337
            return this.filter;
17 ilm 1338
        }
1339
    }
1340
 
41 ilm 1341
    @Override
17 ilm 1342
    public final SQLElementDirectory getDirectory() {
83 ilm 1343
        return this.directory.get();
17 ilm 1344
    }
1345
 
41 ilm 1346
    public final ProductInfo getProductInfo() {
83 ilm 1347
        synchronized (this.restLock) {
1348
            return this.productInfo;
1349
        }
41 ilm 1350
    }
1351
 
17 ilm 1352
    @Override
41 ilm 1353
    public final String getAppName() {
73 ilm 1354
        final ProductInfo productInfo = this.getProductInfo();
1355
        if (productInfo != null)
1356
            return productInfo.getName();
1357
        else
1358
            return this.getProperty("app.name");
17 ilm 1359
    }
1360
 
1361
    @Override
1362
    public final File getWD() {
1363
        synchronized (this.restLock) {
1364
            if (this.wd == null)
1365
                this.setWD(this.createWD());
83 ilm 1366
            return this.wd;
17 ilm 1367
        }
1368
    }
1369
 
142 ilm 1370
    @Override
1371
    public BaseDirs getBaseDirs() {
1372
        synchronized (this.restLock) {
1373
            if (this.baseDirs == null)
1374
                this.baseDirs = this.createBaseDirs();
1375
            return this.baseDirs;
1376
        }
1377
    }
1378
 
177 ilm 1379
    @Override
1380
    public Locale getLocale() {
1381
        synchronized (this.restLock) {
1382
            return this.locale;
1383
        }
1384
    }
1385
 
17 ilm 1386
    // *** setters
1387
 
1388
    // MAYBE add synchronized (not necessary since they're private, and only called with the lock)
1389
 
41 ilm 1390
    private final void setFilter(final SQLFilter filter) {
17 ilm 1391
        this.filter = filter;
1392
    }
1393
 
41 ilm 1394
    private void setServer(final SQLServer server) {
17 ilm 1395
        this.server = server;
1396
    }
1397
 
41 ilm 1398
    private final void setRoot(final DBRoot root) {
17 ilm 1399
        this.root = root;
83 ilm 1400
        checkDestroyed();
73 ilm 1401
        // be sure to try to set a manager to avoid giving all permissions to everyone
1402
        this.urMngr = createUserRightsManager(root);
144 ilm 1403
        this.uMngr = UserManager.getSingletonManager().setInstanceIfNone(root);
17 ilm 1404
    }
1405
 
41 ilm 1406
    private final void setWD(final File dir) {
17 ilm 1407
        this.wd = dir;
1408
    }
73 ilm 1409
 
177 ilm 1410
    public void setLocale(Locale locale) {
1411
        Objects.requireNonNull(locale);
1412
        // don't create the directory
1413
        final boolean localeChangedAndDirExists;
1414
        synchronized (this.restLock) {
1415
            if (locale.equals(this.locale)) {
1416
                localeChangedAndDirExists = false;
1417
            } else {
1418
                this.locale = locale;
1419
                localeChangedAndDirExists = this.directory.isComputeStarted();
1420
            }
1421
        }
1422
        if (localeChangedAndDirExists) {
1423
            final SQLElementDirectory dir = this.getDirectory();
1424
            dir.setTranslator(createTranslator(dir));
1425
        }
1426
    }
1427
 
73 ilm 1428
    public FieldMapper getFieldMapper() {
1429
        return fieldMapper;
1430
    }
1431
 
93 ilm 1432
    public void setFieldMapper(FieldMapper fieldMapper) {
73 ilm 1433
        this.fieldMapper = fieldMapper;
1434
    }
17 ilm 1435
}