Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.compat.cdc / org.gvsig.fmap.dal / org.gvsig.fmap.dal.db / org.gvsig.fmap.dal.db.jdbc / src / main / java / org / gvsig / fmap / dal / store / jdbc2 / spi / operations / FetchFeatureTypeOperation.java @ 46098

History | View | Annotate | Download (20.9 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2020 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 3
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 * For any additional information, do not hesitate to contact us
22
 * at info AT gvsig.com, or visit our website www.gvsig.com.
23
 */
24
package org.gvsig.fmap.dal.store.jdbc2.spi.operations;
25

    
26
import java.sql.Connection;
27
import java.sql.DatabaseMetaData;
28
import java.sql.ResultSet;
29
import java.sql.ResultSetMetaData;
30
import java.sql.SQLException;
31
import java.sql.Statement;
32
import java.util.ArrayList;
33
import java.util.HashMap;
34
import java.util.List;
35
import java.util.Map;
36
import org.apache.commons.collections.CollectionUtils;
37
import org.apache.commons.lang3.StringUtils;
38
import org.cresques.cts.IProjection;
39
import org.gvsig.expressionevaluator.ExpressionBuilder;
40
import org.gvsig.fmap.dal.DataTypes;
41
import org.gvsig.fmap.dal.exception.DataException;
42
import org.gvsig.fmap.dal.feature.EditableFeatureAttributeDescriptor;
43
import org.gvsig.fmap.dal.feature.EditableFeatureType;
44
import org.gvsig.fmap.dal.store.jdbc2.spi.JDBCSQLBuilderBase;
45
import org.gvsig.fmap.dal.store.jdbc2.JDBCHelper;
46
import org.gvsig.fmap.dal.store.jdbc2.JDBCUtils;
47
import org.gvsig.fmap.dal.store.jdbc2.OperationsFactory.TableReference;
48
import org.gvsig.fmap.geom.Geometry;
49
import org.gvsig.fmap.geom.GeometryLocator;
50
import org.gvsig.fmap.geom.type.GeometryType;
51
import static org.gvsig.fmap.dal.store.jdbc2.spi.operations.AbstractConnectionOperation.LOGGER;
52
import org.gvsig.tools.dataTypes.DataType;
53

    
54
@SuppressWarnings("UseSpecificCatch")
55
public class FetchFeatureTypeOperation extends AbstractConnectionOperation {
56

    
57
    protected static class IndexInformation {
58
        String column_name;
59
        boolean ascending;
60
        boolean unique;
61
    }
62

    
63
    protected final EditableFeatureType featureType;
64
    protected final TableReference table;
65
    protected final List<String> primaryKeys;
66
    protected final String defaultGeometryColumn;
67
    protected final IProjection crs;
68
    protected final int geometryType;
69
    protected final int geometrySubtype;
70

    
71
    protected Map<String,IndexInformation> indexesInformation;
72

    
73
    public FetchFeatureTypeOperation(
74
            JDBCHelper helper
75
        ) {
76
        this(helper, null, null, null, null, null, Geometry.TYPES.UNKNOWN, Geometry.SUBTYPES.UNKNOWN);
77
    }
78
    
79
    public FetchFeatureTypeOperation(
80
            JDBCHelper helper,
81
            EditableFeatureType featureType,
82
            String defaultGeometryColumn,
83
            IProjection crs
84
        ) {
85
        this(helper, featureType, null, null, defaultGeometryColumn, crs, Geometry.TYPES.UNKNOWN, Geometry.SUBTYPES.UNKNOWN);
86
    }
87

    
88
    public FetchFeatureTypeOperation(
89
            JDBCHelper helper,
90
            EditableFeatureType featureType,
91
            TableReference table,
92
            List<String> primaryKeys,
93
            String defaultGeometryColumn,
94
            IProjection crs,
95
            int geometryType,
96
            int geometrySubtype
97
        ) {
98
        super(helper);
99
        this.featureType = featureType;
100
        this.table = table;
101
        this.primaryKeys = primaryKeys;
102
        this.defaultGeometryColumn = defaultGeometryColumn;
103
        this.crs = crs;
104
        this.geometryType = geometryType;
105
        this.geometrySubtype = geometrySubtype;
106
    }
107
    
108
    @Override
109
    public final Object perform(Connection conn) throws DataException {
110
        this.fetch(conn);
111
        return true;
112
    }
113
    
114
    protected TableReference getTable() {
115
        return this.table;
116
    }
117
    
118
    public void fetch(Connection conn) throws DataException {
119
        List<String> pks = this.primaryKeys;
120
        Statement st = null;
121
        ResultSet rs = null;
122
        try {
123
            if (CollectionUtils.isEmpty(pks)) {
124
                if (table.hasSubquery()) {
125
                    LOGGER.debug("Searching pk in a table with a subquery ("+table.toString()+".");
126
                }
127
                pks = this.getPrimaryKeysFromMetadata(conn, null, table.getSchema(), table.getTable());
128
                if (CollectionUtils.isEmpty(pks)) {
129
                    pks = getPrimaryKeysFromInformationSchema(conn);
130
                }
131
            }
132
            st = conn.createStatement();
133
            st.setFetchSize(1);
134
            rs = JDBCUtils.executeQuery(st, this.getSQLToRetrieveFirstRowOfTable());
135
            ResultSetMetaData rsMetadata = rs.getMetaData();
136

    
137
            fetchFeatureTypeFromMetadata(conn, rsMetadata, pks);
138

    
139
        } catch (SQLException ex) {
140
            throw new RuntimeException("Can't fecth feature type.",ex);
141
        } finally {
142
            JDBCUtils.closeQuietly(rs);
143
            JDBCUtils.closeQuietly(st);
144
        }
145
    }
146
    
147
    public String getSQLToRetrieveFirstRowOfTable() {
148
      JDBCSQLBuilderBase sqlbuilder = this.createSQLBuilder();
149
      sqlbuilder.select().column().all();
150
      sqlbuilder.select().from().table()
151
          .database(this.table.getDatabase())
152
          .schema(this.table.getSchema())
153
          .name(this.table.getTable());
154
//      sqlbuilder.select().from().subquery(this.table.getSubquery());
155
      sqlbuilder.select().limit(1);
156

    
157
      String sql = sqlbuilder.toString();
158
      return sql;
159
    }
160
    
161
    
162
    public void fetchFeatureTypeFromMetadata(Connection conn, ResultSetMetaData rsMetadata) throws SQLException {
163
        this.fetchFeatureTypeFromMetadata(conn, rsMetadata, new ArrayList<>());
164
    }
165

    
166
    protected void fetchFeatureTypeFromMetadata(Connection conn, ResultSetMetaData rsMetadata, List<String> pks) throws SQLException {
167
        int i;
168
        int geometriesColumns = 0;
169
        String lastGeometry = null;
170

    
171
        EditableFeatureAttributeDescriptor attr;
172
        boolean firstGeometryAttrFound = false;
173
        for (i = 1; i <= rsMetadata.getColumnCount(); i++) {
174
            attr = getAttributeFromMetadata(featureType, conn, rsMetadata, i);
175
            if ( isInPrimaryKeys(pks,attr) ) {
176
                attr.setIsPrimaryKey(true);
177
            }
178
            if (attr.getType() == DataTypes.GEOMETRY) {
179
                geometriesColumns++;
180
                lastGeometry = attr.getName();
181
                // Set the default geometry attribute if it is the one
182
                // given as parameter or it is the first one, just in case.
183
                if (!firstGeometryAttrFound || StringUtils.equalsIgnoreCase(lastGeometry, defaultGeometryColumn)) {
184
                    firstGeometryAttrFound = true;
185
                    featureType.setDefaultGeometryAttributeName(lastGeometry);
186
                }
187
            }
188

    
189
        }
190
        if (StringUtils.isBlank(defaultGeometryColumn)) {
191
            if (geometriesColumns == 1) {
192
                featureType.setDefaultGeometryAttributeName(lastGeometry);
193
            }
194
        } else if (!StringUtils.equalsIgnoreCase(defaultGeometryColumn, featureType.getDefaultGeometryAttributeName())) {
195
            EditableFeatureAttributeDescriptor geomattr = featureType.getEditableAttributeDescriptor(defaultGeometryColumn);
196
            if (geomattr.getDataType().getType() != DataTypes.GEOMETRY) {
197
                geomattr.setDataType(DataTypes.GEOMETRY);
198
                geomattr.setGeometryType(Geometry.TYPES.GEOMETRY, Geometry.SUBTYPES.GEOM2D);
199
            }
200
            
201
        }
202
        
203
        if (featureType.getDefaultGeometryAttribute() != null) {
204
            EditableFeatureAttributeDescriptor attrGeom = (EditableFeatureAttributeDescriptor) featureType.getDefaultGeometryAttribute();
205
            if (crs != null) {
206
                attrGeom.setSRS(crs);
207
            }
208
            if (geometryType != Geometry.TYPES.UNKNOWN && geometrySubtype != Geometry.SUBTYPES.UNKNOWN) {
209
                attrGeom.setGeometryType(geometryType, geometrySubtype);
210
            } else if (geometryType != Geometry.TYPES.UNKNOWN) {
211
                attrGeom.setGeometryType(geometryType, attrGeom.getGeomType().getSubType());
212
            } else if (geometrySubtype != Geometry.SUBTYPES.UNKNOWN) {
213
                attrGeom.setGeometryType(attrGeom.getGeomType().getType(), geometrySubtype);
214
            }
215
        }
216
    }
217

    
218
    protected boolean isInPrimaryKeys(List<String> pks, EditableFeatureAttributeDescriptor attr) {
219
        if( pks == null || attr == null ) {
220
            return false;
221
        }
222
        // En algunos gestores de BBDD, los nombres obtenidos de las pks de los 
223
        // metadados no coinciden con los nombres de los campos ya que unos estan
224
        // en mayusculas y otros en minusculas, asi que en lugar de usar un "contains"
225
        // nos los recorremos y comparamos con IgnoreCase.
226
        for (String pk : pks) {
227
            if( StringUtils.equalsIgnoreCase(pk, attr.getName()) ) {
228
                return true;
229
            }
230
        }
231
        return false;        
232
    }
233
    
234
    protected List<String> getPrimaryKeysFromMetadata(
235
            Connection conn,
236
            String catalog,
237
            String schema,
238
            String table) throws SQLException {
239

    
240
        ResultSet rsPrimaryKeys = null;
241
        ResultSet rs = null;
242
        try {
243
            DatabaseMetaData metadata = conn.getMetaData();
244
            rs = metadata.getTables(catalog, schema, table, null);
245

    
246
            if (!rs.next()) {
247
                // No tables found with default values, ignoring catalog
248
                rs.close();
249
                catalog = null;
250
                schema = null;
251
                rs = metadata.getTables(catalog, schema, table, null);
252
                if (!rs.next()) {
253
                    // table not found
254
                    return null;
255
                } else if (rs.next()) {
256
                    // More that one, cant identify
257
                    return null;
258
                }
259

    
260
            } else if (rs.next()) {
261
                // More that one, cant identify
262
                return null;
263
            }
264
            rsPrimaryKeys = metadata.getPrimaryKeys(catalog, schema, table);
265
            List pks = new ArrayList();
266
            while (rsPrimaryKeys.next()) {
267
                pks.add(rsPrimaryKeys.getString("COLUMN_NAME"));
268
            }
269
            return pks;
270

    
271
        } catch (SQLException e) {
272
            return null;
273

    
274
        } finally {
275
            JDBCUtils.closeQuietly(rs);
276
            JDBCUtils.closeQuietly(rsPrimaryKeys);
277
        }
278

    
279
    }
280

    
281
    protected List<String> getPrimaryKeysFromInformationSchema(Connection conn) throws SQLException {
282

    
283
        String sql = getSQLToRetrievePrimaryKeysFromInformationSchema();
284

    
285
        Statement st = null;
286
        ResultSet rs = null;
287
        List<String> pks = new ArrayList();
288
        try {
289
            st = conn.createStatement();
290
            rs = JDBCUtils.executeQuery(st, sql);
291
            while (rs.next()) {
292
                pks.add(rs.getString(1));
293
            }
294
            if (pks.isEmpty()) {
295
                return null;
296
            }
297
            return pks;
298

    
299
        } catch (Exception ex) {
300
            return pks;
301
            
302
        } finally {
303
            JDBCUtils.closeQuietly(rs);
304
            JDBCUtils.closeQuietly(st);
305
        }
306
    }
307

    
308
    public String getSQLToRetrievePrimaryKeysFromInformationSchema() throws SQLException {
309
        JDBCSQLBuilderBase sqlbuilder = this.createSQLBuilder();
310
        ExpressionBuilder expbuilder = sqlbuilder.expression();
311

    
312
        sqlbuilder.select().column().name("COLUMN_NAME");
313
        sqlbuilder.select().column().name("CONSTRAINT_TYPE");
314
        sqlbuilder.select().from().custom(
315
                "INFORMATION_SCHEMA.table_constraints t_cons "
316
                + "inner join INFORMATION_SCHEMA.key_column_usage c on "
317
                + "c.constraint_catalog = t_cons.constraint_catalog and "
318
                + "c.table_schema = t_cons.table_schema and "
319
                + "c.table_name = t_cons.table_name and "
320
                + "c.constraint_name = t_cons.constraint_name "
321
        );
322
        sqlbuilder.select().where().set(
323
                expbuilder.like(
324
                        expbuilder.custom("c.TABLE_NAME"), 
325
                        expbuilder.constant(table.getTable())
326
                )
327
        );
328
        if( table.hasSchema() ) {
329
            sqlbuilder.select().where().and(
330
                    expbuilder.like(
331
                            expbuilder.custom("c.TABLE_SCHEMA"),
332
                            expbuilder.constant(table.getSchema())
333
                    )
334
            );
335
        }
336
//        if (catalog != null) {
337
//            sqlbuilder.select().where().and(
338
//                    expbuilder.like(
339
//                            expbuilder.custom("c.CONSTRAINT_CATALOG"),
340
//                            expbuilder.constant(catalog)
341
//                    )
342
//            );
343
//        }
344
        sqlbuilder.select().where().and(
345
                expbuilder.eq(
346
                        expbuilder.column("CONSTRAINT_TYPE"),
347
                        expbuilder.constant("PRIMARY KEY")
348
                )
349
        );
350
        return sqlbuilder.toString();
351
    }
352
    
353
    
354
    protected EditableFeatureAttributeDescriptor getAttributeFromMetadata(
355
            EditableFeatureType type,
356
            Connection conn,
357
            ResultSetMetaData rsMetadata,
358
            int colIndex
359
        ) throws SQLException {
360

    
361
        EditableFeatureAttributeDescriptor attr = type.add(
362
                rsMetadata.getColumnName(colIndex),
363
                this.getDataTypeFromMetadata(rsMetadata, colIndex)
364
        );
365
        attr.setAllowNull(
366
            rsMetadata.isNullable(colIndex) == ResultSetMetaData.columnNullable
367
        );
368
        attr.setIsAutomatic(rsMetadata.isAutoIncrement(colIndex));
369
        attr.setIsReadOnly(rsMetadata.isReadOnly(colIndex));
370
        switch(attr.getType()) {
371
            case DataTypes.STRING:
372
              attr.setSize(rsMetadata.getPrecision(colIndex));
373
              attr.setPrecision(DataType.PRECISION_NONE);
374
              attr.setScale(DataType.SCALE_NONE);
375
              break;
376
            case DataTypes.BYTE:
377
              attr.setDisplaySize(rsMetadata.getColumnDisplaySize(colIndex));
378
              attr.setPrecision(DataType.BYTE_DEFAULT_PRECISION);
379
              attr.setScale(DataType.SCALE_NONE);
380
              break;
381
            case DataTypes.INT:
382
              attr.setDisplaySize(rsMetadata.getColumnDisplaySize(colIndex));
383
              attr.setPrecision(DataType.INT_DEFAULT_PRECISION);
384
              attr.setScale(DataType.SCALE_NONE);
385
              break;
386
            case DataTypes.LONG:
387
              attr.setDisplaySize(rsMetadata.getColumnDisplaySize(colIndex));
388
              attr.setPrecision(DataType.LONG_DEFAULT_PRECISION);
389
              attr.setScale(DataType.SCALE_NONE);
390
              break;
391
            case DataTypes.FLOAT:
392
              attr.setDisplaySize(rsMetadata.getColumnDisplaySize(colIndex));
393
              attr.setPrecision(DataType.FLOAT_DEFAULT_PRECISION);
394
              attr.setScale(DataType.SCALE_NONE);
395
              break;
396
            case DataTypes.DOUBLE:
397
              attr.setDisplaySize(rsMetadata.getColumnDisplaySize(colIndex));
398
              attr.setPrecision(DataType.DOUBLE_DEFAULT_PRECISION);
399
              attr.setScale(DataType.SCALE_NONE);
400
              break;
401
            case DataTypes.DECIMAL:
402
              attr.setDisplaySize(rsMetadata.getColumnDisplaySize(colIndex));
403
              attr.setScale(rsMetadata.getScale(colIndex));
404
              attr.setPrecision(rsMetadata.getPrecision(colIndex));
405
              break;
406
            case DataTypes.OBJECT:
407
                attr.setAdditionalInfo(
408
                        "SQLType",
409
                        String.valueOf(rsMetadata.getColumnType(colIndex))
410
                );
411
                attr.setAdditionalInfo(
412
                        "SQLTypeName",
413
                        rsMetadata.getColumnTypeName(colIndex)
414
                );
415
                break;
416
            case DataTypes.GEOMETRY:
417
                this.fetchGeometryTypeAndSRS(attr, rsMetadata, colIndex);
418
                break;
419
        }
420
        IndexInformation indexInformation = this.getIndexesInformation(conn).get(attr.getName());
421
        if( indexInformation!=null ) {
422
            attr.setIsIndexed(true);
423
            attr.setIsIndexAscending(indexInformation.ascending);
424
//            attr.setIsIndexUnique(indexInformation.unique);
425
        }
426
        return attr;
427
    }
428
       
429
    protected Map<String,IndexInformation> getIndexesInformation(
430
            Connection conn
431
        ) throws SQLException {
432
        if( this.indexesInformation==null ) {
433
            this.indexesInformation = new HashMap<>();
434
            DatabaseMetaData metaData = conn.getMetaData();
435
            if( metaData!=null ) {
436
                ResultSet rsIndexes = metaData.getIndexInfo(null, this.table.getSchema(), this.table.getTable(), false, false);
437
                if( rsIndexes!=null ) {
438
                    while( rsIndexes.next() ) {
439
                        IndexInformation x = new IndexInformation();
440
                        x.column_name = rsIndexes.getString("COLUMN_NAME");
441
                        String asc_or_desc = rsIndexes.getString("ASC_OR_DESC");
442
                        // ASC_OR_DESC String => column sort sequence, 
443
                        // "A" => ascending, 
444
                        // "D" => descending, 
445
                        // may be null if sort sequence is not supported; 
446
                        // null when TYPE is tableIndexStatistic
447
                        if( StringUtils.isNotBlank(asc_or_desc) ) {
448
                            if( asc_or_desc.equalsIgnoreCase("A") ) {
449
                                x.ascending = true;
450
                            } else {
451
                                x.ascending = false;
452
                            }
453
                        }
454
                        x.unique = !rsIndexes.getBoolean("NON_UNIQUE");
455
                        // NON_UNIQUE boolean => Can index values be non-unique. 
456
                        // false when TYPE is tableIndexStatistic 
457
                        this.indexesInformation.put(x.column_name, x);
458
                    }
459
                }
460
            }
461
        }
462
        return this.indexesInformation;
463
    }    
464

    
465
    protected int getDataTypeFromMetadata(
466
            ResultSetMetaData rsMetadata,
467
            int colIndex
468
        ) throws SQLException {
469

    
470
        switch (rsMetadata.getColumnType(colIndex)) {
471
            case java.sql.Types.TINYINT:
472
                return DataTypes.BYTE;
473

    
474
            case java.sql.Types.SMALLINT:
475
            case java.sql.Types.INTEGER:
476
                return DataTypes.INT;
477

    
478
            case java.sql.Types.BIGINT:
479
                return DataTypes.LONG;
480

    
481
            case java.sql.Types.REAL:
482
            case java.sql.Types.FLOAT:
483
                return DataTypes.FLOAT;
484

    
485
            case java.sql.Types.DOUBLE:
486
                return DataTypes.DOUBLE;
487

    
488
            case java.sql.Types.NUMERIC:
489
            case java.sql.Types.DECIMAL:
490
                return DataTypes.DECIMAL;
491

    
492
            case java.sql.Types.CHAR:
493
            case java.sql.Types.VARCHAR:
494
            case java.sql.Types.LONGVARCHAR:
495
            case java.sql.Types.CLOB:
496
                return DataTypes.STRING;
497

    
498
            case java.sql.Types.DATE:
499
                return DataTypes.DATE;
500

    
501
            case java.sql.Types.TIME:
502
                return DataTypes.TIME;
503

    
504
            case java.sql.Types.TIMESTAMP:
505
                return DataTypes.TIMESTAMP;
506

    
507
            case java.sql.Types.BOOLEAN:
508
            case java.sql.Types.BIT:
509
                return DataTypes.BOOLEAN;
510

    
511
            case java.sql.Types.BLOB:
512
            case java.sql.Types.BINARY:
513
            case java.sql.Types.LONGVARBINARY:
514
                return DataTypes.BYTEARRAY;
515

    
516
            default:
517
                String typeName = rsMetadata.getColumnTypeName(colIndex);
518
                if( "geometry".equalsIgnoreCase(typeName) ) {
519
                    return DataTypes.GEOMETRY;
520
                }
521
                return DataTypes.OBJECT;
522
        }
523
    }
524

    
525
    /**
526
     * Inicializa el tipo, subtipo y SRS del attributo de tipo geometria.
527
     * 
528
     * @param attr
529
     * @param rsMetadata
530
     * @param colIndex 
531
     */
532
    protected void fetchGeometryTypeAndSRS(
533
            EditableFeatureAttributeDescriptor attr,
534
            ResultSetMetaData rsMetadata,
535
            int colIndex
536
        ) {
537
        if( attr.getType()!=DataTypes.GEOMETRY ) {
538
            return;
539
        }
540
        try {
541
            GeometryType geomType = GeometryLocator.getGeometryManager().getGeometryType(
542
                    Geometry.TYPES.GEOMETRY,
543
                    Geometry.SUBTYPES.GEOM2D
544
            );
545
            attr.setGeometryType(geomType);
546
            attr.setSRS((IProjection)null);
547
        } catch (Exception ex) {
548
            LOGGER.warn("Can't get default geometry type.",ex);
549
        }
550
    }
551
    
552
}