OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 80 | Rev 132 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 80 Rev 83
Line 25... Line 25...
25
import org.openconcerto.utils.CollectionUtils;
25
import org.openconcerto.utils.CollectionUtils;
26
import org.openconcerto.utils.FileUtils;
26
import org.openconcerto.utils.FileUtils;
27
import org.openconcerto.utils.Tuple3;
27
import org.openconcerto.utils.Tuple3;
28
import org.openconcerto.utils.cc.CopyOnWriteMap;
28
import org.openconcerto.utils.cc.CopyOnWriteMap;
29
import org.openconcerto.utils.cc.IClosure;
29
import org.openconcerto.utils.cc.IClosure;
-
 
30
import org.openconcerto.utils.cc.ITransformer;
30
import org.openconcerto.utils.change.CollectionChangeEventCreator;
31
import org.openconcerto.utils.change.CollectionChangeEventCreator;
31
 
32
 
32
import java.io.File;
33
import java.io.File;
33
import java.io.IOException;
34
import java.io.IOException;
34
import java.io.Writer;
35
import java.io.Writer;
35
import java.security.AccessController;
36
import java.security.AccessController;
36
import java.security.PrivilegedAction;
37
import java.security.PrivilegedAction;
37
import java.sql.DatabaseMetaData;
38
import java.sql.DatabaseMetaData;
38
import java.sql.ResultSet;
39
import java.sql.ResultSet;
39
import java.sql.SQLException;
40
import java.sql.SQLException;
40
import java.sql.Statement;
41
import java.util.Collection;
41
import java.util.Collections;
42
import java.util.Collections;
42
import java.util.HashMap;
43
import java.util.HashMap;
43
import java.util.HashSet;
44
import java.util.HashSet;
-
 
45
import java.util.LinkedHashMap;
44
import java.util.List;
46
import java.util.List;
45
import java.util.Map;
47
import java.util.Map;
46
import java.util.Map.Entry;
48
import java.util.Map.Entry;
47
import java.util.Set;
49
import java.util.Set;
48
import java.util.logging.Level;
50
import java.util.logging.Level;
Line 51... Line 53...
51
import java.util.regex.Pattern;
53
import java.util.regex.Pattern;
52
 
54
 
53
import net.jcip.annotations.GuardedBy;
55
import net.jcip.annotations.GuardedBy;
54
import net.jcip.annotations.ThreadSafe;
56
import net.jcip.annotations.ThreadSafe;
55
 
57
 
-
 
58
import org.apache.commons.dbutils.ResultSetHandler;
-
 
59
 
56
/**
60
/**
57
 * Une base de donnée SQL. Une base est unique, pour obtenir une instance il faut passer par
61
 * Une base de donnée SQL. Une base est unique, pour obtenir une instance il faut passer par
58
 * SQLServer. Une base permet d'accéder aux tables qui la composent, ainsi qu'à son graphe.
62
 * SQLServer. Une base permet d'accéder aux tables qui la composent, ainsi qu'à son graphe.
59
 * 
63
 * 
60
 * @author ILM Informatique 4 mai 2004
64
 * @author ILM Informatique 4 mai 2004
Line 106... Line 110...
106
     * @param name son nom.
110
     * @param name son nom.
107
     * @param login the login.
111
     * @param login the login.
108
     * @param pass the password.
112
     * @param pass the password.
109
     */
113
     */
110
    SQLBase(SQLServer server, String name, String login, String pass) {
114
    SQLBase(SQLServer server, String name, String login, String pass) {
111
        this(server, name, login, pass, null);
115
        this(server, name, null, login, pass, null);
112
    }
116
    }
113
 
117
 
114
    /**
118
    /**
115
     * Creates a base in <i>server</i> named <i>name</i>.
119
     * Creates a base in <i>server</i> named <i>name</i>.
116
     * <p>
120
     * <p>
117
     * Note: don't use this constructor, use {@link SQLServer#getOrCreateBase(String)}
121
     * Note: don't use this constructor, use {@link SQLServer#getOrCreateBase(String)}
118
     * </p>
122
     * </p>
119
     * 
123
     * 
120
     * @param server its server.
124
     * @param server its server.
121
     * @param name its name.
125
     * @param name its name.
-
 
126
     * @param systemRootInit to initialize the {@link DBSystemRoot} before setting the datasource.
122
     * @param login the login.
127
     * @param login the login.
123
     * @param pass the password.
128
     * @param pass the password.
124
     * @param dsInit to initialize the datasource before any request (eg setting jdbc properties),
129
     * @param dsInit to initialize the datasource before any request (eg setting jdbc properties),
125
     *        can be <code>null</code>.
130
     *        can be <code>null</code>.
126
     */
131
     */
127
    SQLBase(SQLServer server, String name, String login, String pass, IClosure<SQLDataSource> dsInit) {
132
    SQLBase(SQLServer server, String name, IClosure<? super DBSystemRoot> systemRootInit, String login, String pass, IClosure<? super SQLDataSource> dsInit) {
128
        super(server, name);
133
        super(server, name);
129
        if (name == null)
134
        if (name == null)
130
            throw new NullPointerException("null base");
135
            throw new NullPointerException("null base");
131
        this.schemas = new CopyOnWriteMap<String, SQLSchema>();
136
        this.schemas = new CopyOnWriteMap<String, SQLSchema>();
132
        this.dbVersion = null;
137
        this.dbVersion = null;
133
 
138
 
134
        // if this is the systemRoot we must init the datasource to be able to loadTables()
139
        // if this is the systemRoot we must init the datasource to be able to loadTables()
135
        final DBSystemRoot sysRoot = this.getDBSystemRoot();
140
        final DBSystemRoot sysRoot = this.getDBSystemRoot();
136
        if (sysRoot.getJDBC() == this)
141
        if (sysRoot.getJDBC() == this)
137
            sysRoot.setDS(login, pass, dsInit);
142
            sysRoot.setDS(systemRootInit, login, pass, dsInit);
138
    }
143
    }
139
 
144
 
140
    final TablesMap init(final boolean readCache) {
145
    final TablesMap init(final boolean readCache) {
141
        try {
146
        try {
142
            return refresh(null, readCache, true);
147
            return refresh(null, readCache, true);
Line 147... Line 152...
147
 
152
 
148
    @Override
153
    @Override
149
    protected synchronized void onDrop() {
154
    protected synchronized void onDrop() {
150
        // allow schemas (and their descendants) to be gc'd even we aren't
155
        // allow schemas (and their descendants) to be gc'd even we aren't
151
        this.schemas.clear();
156
        this.schemas.clear();
-
 
157
        SQLType.remove(this);
152
        super.onDrop();
158
        super.onDrop();
153
    }
159
    }
154
 
160
 
155
    TablesMap refresh(final TablesMap namesToRefresh, final boolean readCache) throws SQLException {
161
    TablesMap refresh(final TablesMap namesToRefresh, final boolean readCache) throws SQLException {
156
        return this.refresh(namesToRefresh, readCache, false);
162
        return this.refresh(namesToRefresh, readCache, false);
Line 562... Line 568...
562
    /**
568
    /**
563
     * Get a metadata.
569
     * Get a metadata.
564
     * 
570
     * 
565
     * @param schema the name of the schema.
571
     * @param schema the name of the schema.
566
     * @param name the name of the meta data.
572
     * @param name the name of the meta data.
567
     * @param shouldTestForTable <code>true</code> if the method should try to test if the table
-
 
568
     *        exists, <code>false</code> to just execute a SELECT. Important for postgreSQL since an
-
 
569
     *        error aborts the whole transaction.
-
 
570
     * @return the requested meta data, can be <code>null</code> (including if
573
     * @return the requested meta data, can be <code>null</code> (including if
571
     *         {@value SQLSchema#METADATA_TABLENAME} does not exist).
574
     *         {@value SQLSchema#METADATA_TABLENAME} does not exist).
572
     */
575
     */
573
    String getFwkMetadata(String schema, String name, final boolean shouldTestForTable) {
576
    String getFwkMetadata(String schema, String name) {
-
 
577
        return getFwkMetadata(Collections.singletonList(schema), name).get(schema);
-
 
578
    }
-
 
579
 
-
 
580
    private final String getSel(final String schema, final String name, final boolean selSchema) {
574
        final SQLName tableName = new SQLName(this.getName(), schema, SQLSchema.METADATA_TABLENAME);
581
        final SQLName tableName = new SQLName(this.getName(), schema, SQLSchema.METADATA_TABLENAME);
575
        final String sel = "SELECT \"VALUE\" FROM " + tableName.quote() + " WHERE \"NAME\"= " + this.quoteString(name);
582
        return "SELECT " + (selSchema ? this.quoteString(schema) + ", " : "") + "\"VALUE\" FROM " + tableName.quote() + " WHERE \"NAME\"= " + this.quoteString(name);
576
        // In postgreSQL once there's an error the transaction is aborted and further queries throw
-
 
577
        // an exception. In H2 and MySQL, the transaction is *not* aborted.
-
 
578
        final SQLSystem system = getServer().getSQLSystem();
-
 
579
        if (shouldTestForTable && system == SQLSystem.POSTGRESQL) {
-
 
580
            final String stringDel = "$sel$";
-
 
581
            if (sel.contains(stringDel))
-
 
582
                throw new IllegalStateException(sel + " contains string delimiter : " + stringDel);
-
 
583
            final String funcName = SQLBase.quoteIdentifier(schema) + ".ifExistText";
-
 
584
            final String query = "create or replace function " + funcName + "(schemaName text, tableName text, doesExist text, doesNotExist text) returns text as $BODY$\n"
-
 
585
            // body
583
    }
586
                    + "declare res text;\nbegin\n"
-
 
587
                    //
-
 
588
                    + "    drop function " + funcName + "(text,text,text,text);\n"
-
 
589
                    //
-
 
590
                    + "    if EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = schemaName and table_name = tableName) then\n"
-
 
591
                    //
-
 
-
 
584
 
592
                    + "        execute doesExist into res; else execute doesNotExist into res;\n"
585
    private final void exec(final Collection<String> schemas, final String name, final ResultSetHandler rsh) {
593
                    //
-
 
594
                    + "    end if;\n"
-
 
595
                    //
-
 
596
                    + "    return res;\nend;\n$BODY$ LANGUAGE plpgsql;\n"
-
 
597
                    //
-
 
598
                    + "select " + funcName + "(" + this.quoteString(schema) + ", " + this.quoteString(SQLSchema.METADATA_TABLENAME) + ", " + stringDel + sel + stringDel + ", 'SELECT NULL')";
-
 
599
            try {
-
 
600
                return this.getDataSource().useConnection(new ConnectionHandlerNoSetup<String, SQLException>() {
586
        this.getDataSource().execute(CollectionUtils.join(schemas, "\nUNION ALL ", new ITransformer<String, String>() {
601
                    @Override
587
            @Override
602
                    public String handle(SQLDataSource ds) throws SQLException, SQLException {
588
            public String transformChecked(String schema) {
603
                        final Statement stmt = ds.getConnection().createStatement();
589
                // schema name needed since missing values will result in missing rows not
604
                        stmt.execute(query);
590
                // null values
605
                        if (!stmt.getMoreResults())
591
                return getSel(schema, name, true);
606
                            throw new IllegalStateException("No result");
592
            }
607
                        return (String) SQLDataSource.SCALAR_HANDLER.handle(stmt.getResultSet());
593
        }), new IResultSetHandler(rsh, false));
608
                    }
594
    }
-
 
595
 
-
 
596
    Map<String, String> getFwkMetadata(final Collection<String> schemas, final String name) {
-
 
597
        if (schemas.isEmpty())
-
 
598
            return Collections.emptyMap();
-
 
599
        final Map<String, String> res = new LinkedHashMap<String, String>();
-
 
600
        CollectionUtils.fillMap(res, schemas);
-
 
601
        final ResultSetHandler rsh = new ResultSetHandler() {
609
                });
602
            @Override
-
 
603
            public Object handle(ResultSet rs) throws SQLException {
610
            } catch (SQLException e) {
604
                while (rs.next()) {
611
                throw new IllegalStateException(e);
605
                    res.put(rs.getString(1), rs.getString(2));
612
            }
606
                }
613
        } else {
-
 
614
            try {
-
 
615
                return (String) this.getDataSource().execute(sel, new IResultSetHandler(SQLDataSource.SCALAR_HANDLER, false));
-
 
616
            } catch (RuntimeException rtExn) {
-
 
617
                // pg transactions are aborted, so let the caller know right away (better than to
-
 
618
                // continue and fail later)
-
 
619
                try {
607
                return null;
620
                    if (system == SQLSystem.POSTGRESQL && this.getDataSource().handlingConnection() && !this.getDataSource().getConnection().getAutoCommit())
-
 
621
                        throw rtExn;
-
 
622
                } catch (SQLException e) {
-
 
623
                    throw new IllegalStateException("Couldn't get auto commit : " + e.getMessage() + " " + e.getSQLState(), rtExn);
-
 
624
                }
608
            }
-
 
609
        };
-
 
610
        try {
-
 
611
            if (this.getDataSource().getTransactionPoint() == null) {
-
 
612
                exec(schemas, name, rsh);
-
 
613
            } else {
625
                final SQLException sqlExn = SQLUtils.findWithSQLState(rtExn);
614
                // If already in a transaction, don't risk aborting it if a table doesn't exist.
-
 
615
                // (it's not strictly required for H2 and MySQL, since the transaction is *not*
626
                // table or view not found
616
                // aborted)
627
                if (sqlExn != null && (sqlExn.getSQLState().equals("42S02") || sqlExn.getSQLState().equals("42P01")))
617
                SQLUtils.executeAtomic(this.getDataSource(), new ConnectionHandlerNoSetup<Object, SQLException>() {
-
 
618
                    @Override
-
 
619
                    public Object handle(SQLDataSource ds) throws SQLException {
-
 
620
                        exec(schemas, name, rsh);
628
                    return null;
621
                        return null;
629
                else
-
 
630
                    throw rtExn;
-
 
631
            }
622
                    }
-
 
623
                }, false);
-
 
624
            }
-
 
625
        } catch (Exception exn) {
-
 
626
            final SQLException sqlExn = SQLUtils.findWithSQLState(exn);
-
 
627
            final boolean tableNotFound = sqlExn != null && (sqlExn.getSQLState().equals("42S02") || sqlExn.getSQLState().equals("42P01"));
-
 
628
            if (!tableNotFound)
-
 
629
                throw new IllegalStateException("Not a missing table exception", sqlExn);
-
 
630
 
-
 
631
            // The following fall back should not currently be needed since the table is created
-
 
632
            // by JDBCStructureSource.getNames(). Even without that most DB should contain the
-
 
633
            // metadata tables.
-
 
634
 
-
 
635
            // if only one schema, there's no ambiguity : just return null value
-
 
636
            // otherwise retry with each single schema to find out which ones are missing
-
 
637
            if (schemas.size() > 1) {
-
 
638
                // this won't loop indefinetly since schemas.size() will be 1
-
 
639
                for (final String schema : schemas)
-
 
640
                    res.put(schema, this.getFwkMetadata(schema, name));
632
        }
641
            }
633
    }
642
        }
-
 
643
        return res;
-
 
644
    }
634
 
645
 
635
    public final String getMDName() {
646
    public final String getMDName() {
636
        return this.getServer().getSQLSystem().getMDName(this.getName());
647
        return this.getServer().getSQLSystem().getMDName(this.getName());
637
    }
648
    }
638
 
649
 
Line 834... Line 845...
834
     * 
845
     * 
835
     * @param s an arbitrary string, eg "salut\ l'ami".
846
     * @param s an arbitrary string, eg "salut\ l'ami".
836
     * @return the quoted form, eg "'salut\ l''ami'".
847
     * @return the quoted form, eg "'salut\ l''ami'".
837
     */
848
     */
838
    public final static String quoteStringStd(String s) {
849
    public final static String quoteStringStd(String s) {
839
        return "'" + singleQuote.matcher(s).replaceAll("''") + "'";
850
        return s == null ? "NULL" : "'" + singleQuote.matcher(s).replaceAll("''") + "'";
840
    }
851
    }
841
 
852
 
842
    /**
853
    /**
843
     * Unquote an SQL string the standard way.
854
     * Unquote an SQL string the standard way.
844
     * <p>
855
     * <p>