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

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
            st = conn.createStatement();
134
            st.setFetchSize(1);
135
            rs = JDBCUtils.executeQuery(st, this.getSQLToRetrieveMetadataOfTable());
136
            ResultSetMetaData rsMetadata = rs.getMetaData();
137

    
138
            fetchFeatureTypeFromMetadata(conn, rsMetadata, pks);
139

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

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

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

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

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

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

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

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

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

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

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

    
280
    }
281

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

    
284
        String sql = getSQLToRetrievePrimaryKeysFromInformationSchema();
285

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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