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 @ 47606

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

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

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

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

    
72
    protected Map<String,IndexInformation> indexesInformation;
73

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

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

    
139
            fetchFeatureTypeFromMetadata(conn, rsMetadata, pks);
140

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

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

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

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

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

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

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

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

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

    
273
        } catch (SQLException e) {
274
            return null;
275

    
276
        } finally {
277
            JDBCUtils.closeQuietly(rs);
278
            JDBCUtils.closeQuietly(rsPrimaryKeys);
279
        }
280

    
281
    }
282

    
283
    protected List<String> getPrimaryKeysFromInformationSchema(JDBCConnection conn) throws SQLException {
284

    
285
        String sql = getSQLToRetrievePrimaryKeysFromInformationSchema();
286

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

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

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

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

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

    
467
    protected int getDataTypeFromMetadata(
468
            ResultSetMetaData rsMetadata,
469
            int colIndex
470
        ) throws SQLException {
471

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

    
476
            case java.sql.Types.SMALLINT:
477
            case java.sql.Types.INTEGER:
478
                return DataTypes.INT;
479

    
480
            case java.sql.Types.BIGINT:
481
                return DataTypes.LONG;
482

    
483
            case java.sql.Types.FLOAT:
484
                return DataTypes.FLOAT;
485

    
486
            case java.sql.Types.REAL:
487
            case java.sql.Types.DOUBLE:
488
                return DataTypes.DOUBLE;
489

    
490
            case java.sql.Types.NUMERIC:
491
            case java.sql.Types.DECIMAL:
492
                return DataTypes.DECIMAL;
493

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

    
500
            case java.sql.Types.DATE:
501
                return DataTypes.DATE;
502

    
503
            case java.sql.Types.TIME:
504
                return DataTypes.TIME;
505

    
506
            case java.sql.Types.TIMESTAMP:
507
                return DataTypes.TIMESTAMP;
508

    
509
            case java.sql.Types.BOOLEAN:
510
            case java.sql.Types.BIT:
511
                return DataTypes.BOOLEAN;
512

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

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

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