OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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