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 / JDBCHelperBase.java @ 45717

History | View | Annotate | Download (31.2 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;
25

    
26
import java.io.File;
27
import java.sql.Blob;
28
import java.sql.Clob;
29
import java.sql.Connection;
30
import java.sql.ResultSet;
31
import java.util.ArrayList;
32
import java.util.HashSet;
33
import java.util.List;
34
import org.apache.commons.io.IOUtils;
35
import org.apache.commons.lang3.ArrayUtils;
36
import org.apache.commons.lang3.StringUtils;
37
import org.apache.commons.lang3.mutable.MutableBoolean;
38
import org.gvsig.expressionevaluator.Code;
39
import org.gvsig.expressionevaluator.ExpressionBuilder;
40
import org.gvsig.expressionevaluator.ExpressionEvaluatorLocator;
41
import org.gvsig.expressionevaluator.ExpressionEvaluatorManager;
42
import org.gvsig.expressionevaluator.Function;
43
import org.gvsig.expressionevaluator.GeometryExpressionBuilderHelper.GeometrySupportType;
44
import org.gvsig.expressionevaluator.SymbolTable;
45
import static org.gvsig.fmap.dal.DataManager.FUNCTION_EXISTS;
46
import static org.gvsig.fmap.dal.DataManager.FUNCTION_FOREING_VALUE;
47
import org.gvsig.fmap.dal.DataTypes;
48
import org.gvsig.fmap.dal.SQLBuilder;
49
import org.gvsig.fmap.dal.exception.DataException;
50
import org.gvsig.fmap.dal.exception.InitializeException;
51
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
52
import org.gvsig.fmap.dal.feature.FeatureQueryOrder;
53
import org.gvsig.fmap.dal.feature.FeatureType;
54
import org.gvsig.fmap.dal.feature.ForeingKey;
55
import org.gvsig.fmap.dal.feature.spi.FeatureProvider;
56
import org.gvsig.fmap.dal.feature.spi.SQLBuilderBase;
57
import org.gvsig.fmap.dal.resource.exception.AccessResourceException;
58
import org.gvsig.fmap.dal.resource.spi.ResourceConsumer;
59
import org.gvsig.fmap.dal.resource.spi.ResourceProvider;
60
import org.gvsig.fmap.dal.serverexplorer.filesystem.FilesystemStoreParameters;
61
import org.gvsig.fmap.dal.spi.DataServerExplorerProviderServices;
62
import org.gvsig.fmap.dal.spi.DataStoreProviderServices;
63
import org.gvsig.fmap.dal.spi.DataTransactionServices;
64
import org.gvsig.fmap.dal.store.db.DBHelper;
65
import org.gvsig.fmap.dal.store.jdbc.JDBCConnectionParameters;
66
import org.gvsig.fmap.dal.store.jdbc.JDBCLibrary;
67
import org.gvsig.fmap.dal.store.jdbc.JDBCNewStoreParameters;
68
import org.gvsig.fmap.dal.store.jdbc.JDBCResource;
69
import org.gvsig.fmap.dal.store.jdbc.JDBCServerExplorerParameters;
70
import org.gvsig.fmap.dal.store.jdbc.JDBCStoreParameters;
71
import org.gvsig.fmap.dal.store.jdbc.exception.JDBCCantFetchValueException;
72
import org.gvsig.fmap.dal.store.jdbc2.JDBCHelper;
73
import org.gvsig.fmap.dal.store.jdbc2.JDBCServerExplorer;
74
import org.gvsig.fmap.dal.store.jdbc2.JDBCStoreProvider;
75
import org.gvsig.fmap.dal.store.jdbc2.JDBCUtils;
76
import org.gvsig.fmap.dal.store.jdbc2.OperationsFactory;
77
import org.gvsig.fmap.dal.store.jdbc2.ResulSetControler;
78
import org.gvsig.fmap.dal.store.jdbc2.ResulSetControler.ResultSetEntry;
79
import org.gvsig.fmap.dal.store.jdbc2.impl.ResulSetControlerBase;
80
import org.gvsig.fmap.dal.store.jdbc2.spi.operations.OperationsFactoryBase;
81
import org.gvsig.fmap.geom.Geometry;
82
import org.gvsig.fmap.geom.GeometryLocator;
83
import org.gvsig.fmap.geom.GeometryManager;
84
import org.gvsig.tools.dispose.impl.AbstractDisposable;
85
import org.gvsig.tools.evaluator.Evaluator;
86
import org.gvsig.tools.exception.BaseException;
87
import org.gvsig.tools.exception.NotYetImplemented;
88
import org.gvsig.tools.visitor.VisitCanceledException;
89
import org.gvsig.tools.visitor.Visitor;
90
import org.slf4j.Logger;
91
import org.slf4j.LoggerFactory;
92

    
93
@SuppressWarnings("UseSpecificCatch")
94
public class JDBCHelperBase extends AbstractDisposable implements ResourceConsumer, JDBCHelper {
95

    
96
  private static final boolean ALLOW_AUTOMATIC_VALUES = true;
97
  private static final String QUOTE_FOR_USE_IN_IDENTIFIERS = "\"";
98
  private static final String QUOTE_FOR_USE_IN_STRINGS = "'";
99

    
100
  private static final Logger LOGGER = LoggerFactory.getLogger(JDBCHelperBase.class);
101

    
102
  private ResulSetControler resulSetControler = null;
103

    
104
  // Quien ha creado este helper.
105
  // Se le reenviaran las notificaciones del recurso asociado al helper.
106
  private ResourceConsumer helperClient = null;
107

    
108
  private GeometryManager geometryManager = null;
109

    
110
  private JDBCConnectionParameters connectionParameters;
111

    
112
  private JDBCStoreProvider store;
113

    
114
  protected OperationsFactory operationsFactory = null;
115

    
116
  protected SRSSolver srssolver;
117
  
118
  protected FeatureType providerFeatureType = null;
119
  
120
  protected DataTransactionServices transaction;
121

    
122
  public JDBCHelperBase(JDBCConnectionParameters connectionParameters) {
123
        if (connectionParameters == null) {
124
            throw new IllegalArgumentException("Connection parameters can't be null.");
125
        }
126
        this.connectionParameters = connectionParameters;
127

    
128
        // If a particular treatment is required to convert SRS to the 
129
        // BBDD format, overwrite JDBCSRSsBase and use it instead of JDBCSRSsDumb.
130
        this.srssolver = new SRSSolverDumb(this);
131
    }
132

    
133
  protected void initialize(
134
          ResourceConsumer helperClient,
135
          JDBCConnectionParameters connectionParameters,
136
          JDBCStoreProvider store
137
  ) {
138
    this.store = store;
139
    this.helperClient = helperClient;
140
    this.connectionParameters = connectionParameters;
141
    initializeResource(connectionParameters);
142
  }
143

    
144
  protected String getResourceType() {
145
    return JDBCResource.NAME;
146
  }
147

    
148
  @Override
149
  public String getProviderName() {
150
    return JDBCLibrary.NAME;
151
  }
152

    
153
  @Override
154
  public GeometrySupportType getGeometrySupportType() {
155
    // El proveedor generico de JDBC guadara las geoemtrias como WKT
156
    return GeometrySupportType.WKT;
157
  }
158
  
159
    /**
160
     * @return the providerFeatureType
161
     */
162
  @Override
163
    public FeatureType getProviderFeatureType() {
164
        return providerFeatureType;
165
    }
166

    
167
    /**
168
     * @param providerFeatureType the providerFeatureType to set
169
     */
170
  @Override
171
    public void setProviderFeatureType(FeatureType providerFeatureType) {
172
        this.providerFeatureType = providerFeatureType;
173
    }
174

    
175

    
176
  @Override
177
  public boolean hasSpatialFunctions() {
178
    // Por defecto el proveedor generico de JDBC asume que la BBDD no 
179
    // tiene soporte para funciones espaciales.
180
    return false;
181
  }
182

    
183
  @Override
184
  public boolean allowNestedOperations() {
185
    return false;
186
  }
187

    
188
  @Override
189
  public boolean canWriteGeometry(int geometryType, int geometrySubtype) {
190
    // Como va a guardar las geometrias en WKT, puede escribirlas todas.
191
    return true;
192
  }
193

    
194
  @Override
195
  public JDBCSQLBuilderBase createSQLBuilder() {
196
    return new JDBCSQLBuilderBase(this);
197
  }
198

    
199
  @Override
200
  public String getQuoteForIdentifiers() {
201
    return QUOTE_FOR_USE_IN_IDENTIFIERS;
202
  }
203

    
204
  @Override
205
  public boolean allowAutomaticValues() {
206
    return ALLOW_AUTOMATIC_VALUES;
207
  }
208

    
209
  @Override
210
  public boolean supportOffsetInSelect() {
211
    // Asumimos que la BBDD soporta OFFSET
212
    return true;
213
  }
214

    
215
  @Override
216
  public String getQuoteForStrings() {
217
    return QUOTE_FOR_USE_IN_STRINGS;
218
  }
219

    
220
  protected boolean supportCaller(Code.Callable caller) {
221
    if (StringUtils.equalsIgnoreCase(caller.name(), "FOREING_VALUE")) {
222
      return true;
223
    }
224
    Function function = caller.function();
225
    if (function == null) {
226
      return false;
227
    }
228
    if (!function.isSQLCompatible()) {
229
      return false;
230
    }
231
    if (!this.hasSpatialFunctions()) {
232
      if (StringUtils.equalsIgnoreCase(function.group(), Function.GROUP_OGC)) {
233
        return false;
234
      }
235
    }
236
    return true;
237
  }
238

    
239
  @Override
240
  public boolean supportFilter(final FeatureType type, Evaluator filter) {
241
    // No podemos filtrar cuando:
242
    // - Hay una subquery especificada en los parametros 
243
    // - Si hay un filtro y el getSQL devuelbe null.
244
    // - Si se esta usando alguna funcion no-sql en el getSQL
245
    // - Si se estan usando funciones OGC y no soportamos funciones espaciales
246
    // - Si se esta usando algun campo calculado en la expresion de filtro.
247
    // 
248
    // Un proveedor especifico podria sobreescribir el metodo,
249
    // para hilar mas fino al comprobar si soporta el filtro o no.
250
    //
251

    
252
    if (this.useSubquery()) {
253
      // Se esta usando una subquery en los parametros de acceso a la
254
      // BBDD, asi que no podemos filtrar.
255
      return false;
256
    }
257
    if (filter == null) {
258
      // No hay que filtrar nada, asi que se p?ede.
259
      return true;
260
    }
261
    String sql = filter.getSQL();
262
    if (StringUtils.isEmpty(sql)) {
263
      // Hay un filtro, pero la SQL es null, con lo que no podemos
264
      // filtrar por el.
265
      return false;
266
    }
267

    
268
    // Ahora vamos a comprobar que las funciones que se usan son 
269
    // compatibles sql, y que no se usan funciones OGC si el 
270
    // proveedor dice que no soporta funciones espaciales.
271
    // Tambien comprobaremos que el filtro no usa ningun campo calculado.
272
    final MutableBoolean isCompatible = new MutableBoolean(true);
273
    ExpressionEvaluatorManager manager = ExpressionEvaluatorLocator.getManager();
274
    Code code = manager.compile(sql);
275
    SymbolTable symbolTable = manager.createSymbolTable();
276
    code.link(symbolTable);
277
    try {
278
      code.accept(new Visitor() {
279
        @Override
280
        public void visit(Object code_obj) throws VisitCanceledException, BaseException {
281
          Code code = (Code) code_obj;
282
          switch (code.code()) {
283
            case Code.CALLABLE:
284
              Code.Callable caller = (Code.Callable) code;
285
              if (!supportCaller(caller)) {
286
                isCompatible.setValue(false);
287
                throw new VisitCanceledException();
288
              }
289
              break;
290

    
291
            case Code.IDENTIFIER:
292
              Code.Identifier identifier = (Code.Identifier) code;
293
              if (type != null) {
294
                FeatureAttributeDescriptor attrdesc = type.getAttributeDescriptor(identifier.name());
295
                if (attrdesc != null) {
296
                  if (attrdesc.isComputed()) {
297
                    isCompatible.setValue(false);
298
                    throw new VisitCanceledException();
299
                  }
300
                }
301
              }
302
              break;
303
          }
304
        }
305
      });
306

    
307
    } catch (VisitCanceledException ex) {
308
      // Do nothing
309
    } catch (Exception ex) {
310
      LOGGER.warn("Can't calculate if is SQL compatible.", ex);
311
    }
312

    
313
    return isCompatible.booleanValue();
314
  }
315

    
316
  @Override
317
  public boolean supportOrder(FeatureType type, FeatureQueryOrder order) {
318
    if (this.useSubquery()) {
319
      return false;
320
    }
321
    if (order == null) {
322
      return true;
323
    }
324
    for (FeatureQueryOrder.FeatureQueryOrderMember member : order.members()) {
325
      if (member.hasEvaluator()) {
326
        if (!this.supportFilter(type, member.getEvaluator())) {
327
          return false;
328
        }
329
      }
330
    }
331
    return true;
332
  }
333

    
334
  @Override
335
  public OperationsFactory getOperations() {
336
    if (this.operationsFactory == null && !isClosed()) {
337
      this.operationsFactory = new OperationsFactoryBase(this);
338
    }
339
    return operationsFactory;
340
  }
341

    
342
  protected void initializeResource(JDBCConnectionParameters params) {
343
//        Object[] resourceParams = new Object[]{
344
//            params.getUrl(),
345
//            params.getHost(),
346
//            params.getPort(),
347
//            params.getDBName(),
348
//            params.getUser(),
349
//            params.getPassword(),
350
//            params.getJDBCDriverClassName()
351
//        };
352
//
353
//        try {
354
//            ResourceManagerProviderServices manager
355
//                    = (ResourceManagerProviderServices) DALLocator.getResourceManager();
356
//            JDBCResource resource = (JDBCResource) manager.createAddResource(
357
//                    this.getResourceType(),
358
//                    resourceParams
359
//            );
360
//            this.resource = resource;
361
//            this.resource.addConsumer(this);
362
//        } catch (InitializeException ex) {
363
//            logger.trace("Can't initialize resource (" + ArrayUtils.toString(resourceParams) + ").", ex);
364
//            throw new RuntimeException("Can't initialize resource (" + ArrayUtils.toString(resourceParams) + ").", ex);
365
//        }
366

    
367
  }
368

    
369
  @Override
370
  public String getSourceId() {
371
    return this.store.getSourceId();
372
  }
373

    
374
  @Override
375
  public JDBCResource getResource() {
376
    return null;
377
//        return this.resource;
378
  }
379

    
380
  @Override
381
  public Connection getConnection() throws AccessResourceException {
382
    throw new NotYetImplemented();
383
  }
384

    
385
  @Override
386
  public Connection getConnectionWritable() throws AccessResourceException {
387
    return this.getConnection();
388
  }
389

    
390
  @Override
391
  public String getConnectionURL() {
392
    return null;
393
  }
394

    
395
  @Override
396
  public JDBCConnectionParameters getConnectionParameters() {
397
    return connectionParameters;
398
  }
399

    
400
  @Override
401
  public void closeConnection(Connection connection) {
402
      JDBCUtils.close(connection);
403
  }
404

    
405
  @Override
406
  public void closeConnectionQuietly(Connection connection) {
407
      JDBCUtils.closeQuietly(connection);
408
  }
409

    
410
  @Override
411
  protected void doDispose() throws BaseException {
412
    JDBCUtils.closeQuietly(this);
413
  }
414

    
415
  @Override
416
  public void close() throws Exception {
417
    JDBCUtils.closeQuietly(this.resulSetControler);
418
    this.resulSetControler = null;
419
    this.operationsFactory = null;
420
    this.srssolver = null;
421
    this.connectionParameters = null;
422
    this.helperClient = null;
423
    this.geometryManager = null;
424
    this.providerFeatureType = null;
425
    this.store = null;
426
  }
427
  
428
  public boolean isClosed() {
429
      return this.srssolver == null;
430
  }
431

    
432
  @Override
433
  public boolean closeResourceRequested(ResourceProvider resource) {
434
    return this.helperClient.closeResourceRequested(resource);
435
  }
436

    
437
  @Override
438
  public void resourceChanged(ResourceProvider resource) {
439
    this.helperClient.resourceChanged(resource);
440
  }
441

    
442
  @Override
443
  public GeometryManager getGeometryManager() {
444
    if (this.geometryManager == null) {
445
      this.geometryManager = GeometryLocator.getGeometryManager();
446
    }
447
    return this.geometryManager;
448
  }
449

    
450
  @Override
451
  public ResulSetControler getResulSetControler() {
452
    if (this.resulSetControler == null) {
453
      this.resulSetControler = new ResulSetControlerBase(this);
454
    }
455
    return this.resulSetControler;
456
  }
457

    
458
  @Override
459
  public void fetchFeature(FeatureProvider feature, ResultSetEntry rs) throws DataException {
460
    fetchFeature(feature, rs.get(), rs.getColumns(), rs.getExtraValueNames());
461
  }
462

    
463
  @Override
464
  public void fetchFeature(FeatureProvider feature, ResultSet rs, FeatureAttributeDescriptor[] columns, String[] extraValueNames) throws DataException {
465
    Object value;
466
    try {
467
      int rsIndex = 1;
468
      for (FeatureAttributeDescriptor column : columns) {
469
        switch (column.getType()) {
470
          case DataTypes.GEOMETRY:
471
            value = this.getGeometryFromColumn(rs, rsIndex++);
472
            break;
473
          default:
474
            value = rs.getObject(rsIndex++);
475
            if (value instanceof Blob) {
476
              Blob blob = (Blob) value;
477
              value = blob.getBytes(1, (int) blob.length());
478
              blob.free();
479
            } else if(value instanceof Clob) {
480
              Clob clob = (Clob) value;
481
              value = new String(IOUtils.toCharArray(clob.getCharacterStream()));
482
              clob.free();
483
          }
484
        }
485
        feature.set(column.getIndex(), value);
486
      }
487
      if (ArrayUtils.isNotEmpty(extraValueNames)) {
488
        feature.setExtraValueNames(extraValueNames);
489
        for (int index = 0; index < extraValueNames.length; index++) {
490
          value = rs.getObject(rsIndex++);
491
          if (value instanceof Blob) {
492
            Blob blob = (Blob) value;
493
            value = blob.getBytes(0, (int) blob.length());
494
            blob.free();
495
          }
496
          feature.setExtraValue(index, value);
497
        }
498
      }
499
    } catch (Exception ex) {
500
      throw new JDBCCantFetchValueException(ex);
501
    }
502
  }
503

    
504
  @Override
505
  public Geometry getGeometryFromColumn(ResultSetEntry rs, int index) throws DataException {
506
    return getGeometryFromColumn(rs.get(), index);
507
  }
508

    
509
  @Override
510
  public Geometry getGeometryFromColumn(ResultSet rs, int index) throws DataException {
511
    try {
512
      Object value;
513
      switch (this.getGeometrySupportType()) {
514
        case NATIVE:
515
        case WKB:
516
          value = rs.getBytes(index);
517
          if (value == null) {
518
            return null;
519
          }
520
          return this.getGeometryManager().createFrom((byte[]) value);
521

    
522
        case EWKB:
523
          value = rs.getBytes(index);
524
          if (value == null) {
525
            return null;
526
          }
527
          return this.getGeometryManager().createFrom((byte[]) value);
528
        case WKT:
529
        default:
530
          value = rs.getString(index);
531
          if (value == null) {
532
            return null;
533
          }
534
          return this.getGeometryManager().createFrom((String) value);
535

    
536
      }
537
    } catch (Exception ex) {
538
      throw new JDBCCantFetchValueException(ex);
539
    }
540
  }
541

    
542
  @Override
543
  public FeatureProvider createFeature(FeatureType featureType) throws DataException {
544
    return this.store.getStoreServices().createDefaultFeatureProvider(featureType);
545
  }
546

    
547
  @Override
548
  public boolean useSubquery() {
549
    if (this.store == null) {
550
      return false;
551
    }
552
    return !StringUtils.isEmpty(this.store.getParameters().getSQL());
553
  }
554

    
555
  @Override
556
  public SRSSolver getSRSSolver() {
557
    return this.srssolver;
558
  }
559

    
560
  @Override
561
  public JDBCStoreProvider createProvider(
562
          JDBCStoreParameters parameters,
563
          DataStoreProviderServices providerServices
564
  ) throws InitializeException {
565

    
566
    JDBCStoreProviderBase theStore = new JDBCStoreProviderBase(
567
            parameters,
568
            providerServices,
569
            DBHelper.newMetadataContainer(JDBCLibrary.NAME),
570
            this
571
    );
572
    this.initialize(theStore, parameters, theStore);
573
    return theStore;
574
  }
575

    
576
  @Override
577
  public JDBCServerExplorer createServerExplorer(
578
          JDBCServerExplorerParameters parameters,
579
          DataServerExplorerProviderServices providerServices
580
  ) throws InitializeException {
581

    
582
    JDBCServerExplorer explorer = new JDBCServerExplorerBase(
583
            parameters,
584
            providerServices,
585
            this
586
    );
587
    this.initialize(explorer, parameters, null);
588
    return explorer;
589
  }
590

    
591
  @Override
592
  public JDBCNewStoreParameters createNewStoreParameters() {
593
    return new JDBCNewStoreParameters();
594
  }
595

    
596
  @Override
597
  public JDBCStoreParameters createOpenStoreParameters() {
598
    return new JDBCStoreParameters();
599
  }
600
  
601
  @Override
602
  public JDBCStoreParameters createOpenStoreParameters(JDBCServerExplorerParameters parameters) {
603
    JDBCStoreParameters params = this.createOpenStoreParameters();
604
    params.setHost(parameters.getHost());
605
    params.setPort(parameters.getPort());
606
    params.setDBName(parameters.getDBName());
607
    params.setUser(parameters.getUser());
608
    params.setPassword(parameters.getPassword());
609
    params.setCatalog(parameters.getCatalog());
610
    params.setSchema(parameters.getSchema());
611
    params.setJDBCDriverClassName(parameters.getJDBCDriverClassName());
612
    params.setUrl(parameters.getUrl());
613
    if( parameters instanceof FilesystemStoreParameters ) {
614
        File f = ((FilesystemStoreParameters) parameters).getFile();
615
        ((FilesystemStoreParameters) params).setFile(f);
616
    }
617
    return params;
618
  }
619
  
620

    
621
  @Override
622
  public JDBCServerExplorerParameters createServerExplorerParameters() {
623
    return new JDBCServerExplorerParameters();
624
  }
625

    
626
  @Override
627
  public String getSourceId(JDBCStoreParameters parameters) {
628
    return parameters.getHost() + ":"
629
            + parameters.getDBName() + ":"
630
            + parameters.getSchema() + ":"
631
            + parameters.tableID();
632
  }
633

    
634
  @Override
635
  public boolean isThreadSafe() {
636
    return true;
637
  }
638

    
639
  /** 
640
   * This method has been overriden in Oracle provider
641
   * 
642
   * @param sqlbuilder
643
   * @param type
644
   * @param extra_column_names 
645
   */
646
  @Override
647
  public void processSpecialFunctions(
648
          SQLBuilder sqlbuilder,
649
          FeatureType type,
650
          List<String> extra_column_names) {
651
    replaceForeingValueFunction(sqlbuilder, type, extra_column_names);
652
    replaceExistsFunction(sqlbuilder, type, extra_column_names);
653
  }
654

    
655
  private void replaceExistsFunction(
656
          SQLBuilder sqlbuilder,
657
          FeatureType type,
658
          final List<String> extra_column_names) {
659
    
660
    //  Si lse encuentra una construccion del tipo:
661
    //    SELECT ... FROM ... WHERE ... EXISTS(list, 'EXISTS_ID') ...
662
    //  se traslada a:
663
    //    SELECT ... EXISTS(list) AS EXISTS_ID FROM ... WHERE ... EXISTS(list) ...
664
    //  Y se a?ade el valor ESISTS_ID a las columnas extra a recuperar de la consulta.
665
    //          
666
    
667
    final SQLBuilder.SelectBuilder select = sqlbuilder.select();
668
    final ExpressionBuilder where = select.where();
669
    if (where == null || where.isEmpty() || select.has_group_by() ) {
670
      return;
671
    }
672
    final List<ExpressionBuilder.Value[]> value_replacements = new ArrayList<>();
673
    where.accept(new ExpressionBuilder.Visitor() {
674
      @Override
675
      public void visit(ExpressionBuilder.Visitable value) {
676
        if (!(value instanceof ExpressionBuilder.Function)) {
677
          return;
678
        }
679
        ExpressionBuilder.Function function = (ExpressionBuilder.Function) value;
680
        if (!StringUtils.equalsIgnoreCase(function.name(), FUNCTION_EXISTS)) {
681
          return;
682
        }
683
        if (function.parameters().size() != 2) {
684
          return;
685
        }
686
        ExpressionBuilder.Value arg0 = function.parameters().get(0);
687
        ExpressionBuilder.Value arg1 = function.parameters().get(1);
688
        if (arg1 == null) {
689
          return;
690
        }
691
        String columnName = (String) ((ExpressionBuilder.Constant) arg1).value();
692
        SQLBuilder.SelectColumnBuilder column = select.column();
693
        column.value(
694
                sqlbuilder.expression().function(FUNCTION_EXISTS, arg0)
695
        );
696
        column.as(columnName);
697

    
698
        if( extra_column_names!=null ) {
699
          extra_column_names.add(columnName);
700
        }
701
      }
702
    }, null);
703
    if (value_replacements.isEmpty()) {
704
      return;
705
    }
706
    // Realizamos los reemplazos calculados previamente (value_replacements).
707
    for (ExpressionBuilder.Value[] replaceValue : value_replacements) {
708
      ExpressionBuilder.Value target = replaceValue[0];
709
      ExpressionBuilder.Value replacement = replaceValue[1];
710
      sqlbuilder.select().replace(target, replacement);
711
    }
712
  }
713

    
714
  protected void replaceForeingValueFunction(
715
          SQLBuilder sqlbuilder,
716
          FeatureType type,
717
          List<String> extra_column_names) {
718
    try {
719
      // See test SQLBuilderTest->testForeingValue()
720
      final ExpressionBuilder where = sqlbuilder.select().where();
721
//      if (where == null || where.isEmpty()) {
722
//        return;
723
//      }
724
      final SQLBuilder.TableNameBuilder table = sqlbuilder.select().from().table();
725
      final ExpressionBuilder expbuilder = sqlbuilder.expression();
726

    
727
      final List<String> foreing_value_args;
728
      if (extra_column_names == null || sqlbuilder.select().has_group_by()) {
729
        foreing_value_args = new ArrayList<>();
730
      } else {
731
        foreing_value_args = extra_column_names;
732
      }
733
      final List<ExpressionBuilder.Value[]> value_replacements = new ArrayList<>();
734

    
735
      // Buscamos las llamadas a la funcion "foreing_value" y nos quedamos
736
      // el argumento de esta asi como por que tendriamos que sustituirla 
737
      // una vez hechos los left joins que toquen.
738
      sqlbuilder.select().accept(new ExpressionBuilder.Visitor() {
739
        @Override
740
        public void visit(ExpressionBuilder.Visitable value) {
741
          // Requiere que sea la funcion "FOREING_VALUE con un solo 
742
          // argumento que sea una constante de tipo string.
743
          if (!(value instanceof ExpressionBuilder.Function)) {
744
            return;
745
          }
746
          ExpressionBuilder.Function function = (ExpressionBuilder.Function) value;
747
          if (!StringUtils.equalsIgnoreCase(function.name(), FUNCTION_FOREING_VALUE)) {
748
            return;
749
          }
750
          if (function.parameters().size() != 1) {
751
            return;
752
          }
753
          ExpressionBuilder.Value arg = function.parameters().get(0);
754
          if (!(arg instanceof ExpressionBuilder.Constant)) {
755
            return;
756
          }
757
          Object arg_value = ((ExpressionBuilder.Constant) arg).value();
758
          if (!(arg_value instanceof CharSequence)) {
759
            return;
760
          }
761
          String foreing_value_arg = arg_value.toString();
762
          String[] foreingNameParts = StringUtils.split(foreing_value_arg, "[.]");
763
          if (foreingNameParts.length != 2) {
764
            // De momento solo tratamos joins entre dos tablas.
765
            return;
766
          }
767
          String columnNameLocal = foreingNameParts[0];
768
          String columnNameForeing = foreingNameParts[1];
769
          FeatureAttributeDescriptor attr = type.getAttributeDescriptor(columnNameLocal);
770
          if (attr==null) {
771
              throw new RuntimeException("Cannot find in feature type attribute:"+columnNameLocal);
772
          }
773
          if (!attr.isForeingKey()) {
774
            // Uhm... si el argumento no referencia a un campo que es
775
            // clave ajena no lo procesamos. 
776
            // ? Deberiamos lanzar un error ?
777
            return;
778
          }
779
          // Nos guardaremos por que hay que reemplazar la funcion 
780
          // FOREING_VALUE, y su argumento para mas tarde.
781
          ForeingKey foreingKey = attr.getForeingKey();
782
          SQLBuilder.TableNameBuilder foreingTable = sqlbuilder.createTableNameBuilder()
783
                  .database(table.getDatabase())
784
                  .schema(table.getSchema())
785
                  .name(foreingKey.getTableName());
786
          // Reemplzaremos la funcion FOREING_VALUE, por el acceso al campo
787
          // que toca de la tabla a la que referencia la clave ajena.
788
          ExpressionBuilder.Variable function_replacement = sqlbuilder.column(foreingTable, columnNameForeing);
789
          value_replacements.add(
790
                  new ExpressionBuilder.Value[]{
791
                    function,
792
                    function_replacement
793
                  }
794
          );
795
          if (!foreing_value_args.contains(foreing_value_arg)) {
796
            foreing_value_args.add(foreing_value_arg);
797
          }
798
        }
799
      }, null);
800
      
801
      // Si no habia ningun llamada a la funcion FOREING_VALUE, no hay que
802
      // hacer nada.
803
      if (foreing_value_args.isEmpty()) {
804
        return;
805
      }
806

    
807
      // Calculamos que referencias de columnas hemos de cambiar para 
808
      // que no aparezcan ambiguedades al hacer el join (nombres de
809
      // columna de una y otra tabla que coincidan).
810
      // Para las columnas que puedan dar conflicto se prepara un reemplazo 
811
      // de estas que tenga el nombre de tabla.
812
      for (ExpressionBuilder.Variable variable : sqlbuilder.variables()) {
813
        if (variable instanceof SQLBuilderBase.ColumnBase) {
814
          continue;
815
        }
816
        for (String foreingName : foreing_value_args) {
817
          String[] foreingNameParts = foreingName.split("[.]");
818
          if (foreingNameParts.length != 2) {
819
            continue;
820
          }
821
          String columnNameLocal = foreingNameParts[0];
822
          String columnNameForeing = foreingNameParts[1];
823
          if (StringUtils.equalsIgnoreCase(variable.name(), columnNameForeing)
824
                  || StringUtils.equalsIgnoreCase(variable.name(), columnNameLocal)) {
825
            ExpressionBuilder.Variable variable_replacement = sqlbuilder.column(
826
                    table,
827
                    variable.name()
828
            );
829
            value_replacements.add(
830
                    new ExpressionBuilder.Value[]{
831
                      variable,
832
                      variable_replacement
833
                    }
834
            );
835
          }
836
        }
837
      }
838

    
839
      // Realizamos los reemplazos calculados previamente (value_replacements).
840
      for (ExpressionBuilder.Value[] replaceValue : value_replacements) {
841
        ExpressionBuilder.Value target = replaceValue[0];
842
        ExpressionBuilder.Value replacement = replaceValue[1];
843
        sqlbuilder.select().replace(target, replacement);
844
      }
845

    
846
        // A?adimos a la SQL los "LEFT JOIN" que toca para poder acceder
847
        // a los valores referenciados por la funcion FOREING_VALUE.
848
        // Ademas a?adimos los valores referenciados por la funcion FOREING_VALUE
849
        // como columnas de la SQL para poder acceder a ellos si fuese necesario.
850
        HashSet usedLeftJoins = new HashSet();
851
      for (String foreingName : foreing_value_args) {
852
        String[] foreingNameParts = foreingName.split("[.]");
853
        if (foreingNameParts.length != 2) {
854
          continue;
855
        }
856
        String columnNameLocal = foreingNameParts[0];
857
        String columnNameForeing = foreingNameParts[1];
858
        FeatureAttributeDescriptor attr = type.getAttributeDescriptor(columnNameLocal);
859
        if (attr.isForeingKey()) {
860
          ForeingKey foreingKey = attr.getForeingKey();
861
          SQLBuilder.TableNameBuilder foreingTable = sqlbuilder.createTableNameBuilder()
862
                  .database(table.getDatabase())
863
                  .schema(table.getSchema())
864
                  .name(foreingKey.getTableName());
865
          SQLBuilder.TableNameBuilder mainTable = sqlbuilder.createTableNameBuilder()
866
                  .database(table.getDatabase())
867
                  .schema(table.getSchema())
868
                  .name(table.getName());
869
          
870
          if (!usedLeftJoins.contains(foreingTable.getName())) {
871
            sqlbuilder.select().from()
872
                    .left_join(
873
                            foreingTable,
874
                            expbuilder.eq(
875
                                    sqlbuilder.column(mainTable, columnNameLocal),
876
                                    sqlbuilder.column(foreingTable, foreingKey.getCodeName())
877
                            )
878
                    );
879
            usedLeftJoins.add(foreingTable.getName());
880
          }
881
          if (!sqlbuilder.select().has_group_by()) {
882
                sqlbuilder.select().column().name(foreingTable, columnNameForeing);
883
          }
884
        }
885
      }
886
      
887
        for (SQLBuilder.SelectColumnBuilder column : sqlbuilder.select().getColumns()) {
888
            if(column.getTable()==null && column.getName()!=null) {
889
                column.name(table, column.getName());
890
            }
891
        }
892

    
893
    } catch (Throwable th) {
894
      LOGGER.warn("Can't replace FOREING_VALUE function.", th);
895
      throw th;
896
    } finally {
897
      LOGGER.trace("Exit from replaceForeingValueFunction.");
898
    }
899
  }
900

    
901
    @Override
902
    public void setTransaction(DataTransactionServices transaction) {
903
        this.transaction = transaction;
904
    }
905
    
906
    @Override
907
    public String toString() {
908
        try {
909
            return String.format("%s %x %s", this.getClass().getSimpleName(), this.hashCode(), this.connectionParameters.getUrl());
910
        } catch (Exception e) {
911
            return super.toString();
912
        }
913
    }
914

    
915
   
916
}