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

History | View | Annotate | Download (27.4 KB)

1
package org.gvsig.fmap.dal.store.jdbc2.spi;
2

    
3
import java.sql.Blob;
4
import org.gvsig.fmap.dal.store.jdbc2.impl.ResulSetControlerBase;
5
import org.gvsig.fmap.dal.store.jdbc2.spi.operations.OperationsFactoryBase;
6
import java.sql.Connection;
7
import java.sql.ResultSet;
8
import java.util.ArrayList;
9
import java.util.List;
10
import org.apache.commons.lang3.ArrayUtils;
11
import org.apache.commons.lang3.StringUtils;
12
import org.apache.commons.lang3.mutable.MutableBoolean;
13
import org.gvsig.expressionevaluator.Code;
14
import org.gvsig.expressionevaluator.ExpressionBuilder;
15
import org.gvsig.expressionevaluator.ExpressionEvaluatorLocator;
16
import org.gvsig.expressionevaluator.ExpressionEvaluatorManager;
17
import org.gvsig.expressionevaluator.Function;
18
import org.gvsig.expressionevaluator.GeometryExpressionBuilderHelper.GeometrySupportType;
19
import org.gvsig.expressionevaluator.SymbolTable;
20
import static org.gvsig.fmap.dal.DataManager.FUNCTION_EXISTS;
21
import static org.gvsig.fmap.dal.DataManager.FUNCTION_FOREING_VALUE;
22
import static org.gvsig.fmap.dal.DataManager.FUNCTION_SELECT;
23
import org.gvsig.fmap.dal.DataTypes;
24
import org.gvsig.fmap.dal.SQLBuilder;
25
import org.gvsig.fmap.dal.exception.DataException;
26
import org.gvsig.fmap.dal.exception.InitializeException;
27
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
28
import org.gvsig.fmap.dal.feature.FeatureQueryOrder;
29
import org.gvsig.fmap.dal.feature.FeatureType;
30
import org.gvsig.fmap.dal.feature.ForeingKey;
31

    
32
import org.gvsig.fmap.dal.feature.spi.FeatureProvider;
33
import org.gvsig.fmap.dal.feature.spi.SQLBuilderBase;
34
import org.gvsig.fmap.dal.resource.exception.AccessResourceException;
35
import org.gvsig.fmap.dal.resource.spi.ResourceConsumer;
36
import org.gvsig.fmap.dal.resource.spi.ResourceProvider;
37
import org.gvsig.fmap.dal.spi.DataServerExplorerProviderServices;
38
import org.gvsig.fmap.dal.spi.DataStoreProviderServices;
39
import org.gvsig.fmap.dal.store.db.DBHelper;
40
import org.gvsig.fmap.dal.store.jdbc.JDBCConnectionParameters;
41
import org.gvsig.fmap.dal.store.jdbc.JDBCLibrary;
42
import org.gvsig.fmap.dal.store.jdbc.JDBCNewStoreParameters;
43
import org.gvsig.fmap.dal.store.jdbc.JDBCResource;
44
import org.gvsig.fmap.dal.store.jdbc.JDBCServerExplorerParameters;
45
import org.gvsig.fmap.dal.store.jdbc.JDBCStoreParameters;
46
import org.gvsig.fmap.dal.store.jdbc.exception.JDBCCantFetchValueException;
47
import org.gvsig.fmap.dal.store.jdbc2.JDBCHelper;
48
import org.gvsig.fmap.dal.store.jdbc2.JDBCServerExplorer;
49
import org.gvsig.fmap.dal.store.jdbc2.JDBCStoreProvider;
50
import org.gvsig.fmap.dal.store.jdbc2.JDBCUtils;
51
import org.gvsig.fmap.dal.store.jdbc2.OperationsFactory;
52
import org.gvsig.fmap.dal.store.jdbc2.ResulSetControler;
53
import org.gvsig.fmap.dal.store.jdbc2.ResulSetControler.ResultSetEntry;
54
import org.gvsig.fmap.geom.Geometry;
55
import org.gvsig.fmap.geom.GeometryLocator;
56
import org.gvsig.fmap.geom.GeometryManager;
57
import org.gvsig.tools.dispose.impl.AbstractDisposable;
58
import org.gvsig.tools.evaluator.Evaluator;
59
import org.gvsig.tools.exception.BaseException;
60
import org.gvsig.tools.exception.NotYetImplemented;
61
import org.gvsig.tools.visitor.VisitCanceledException;
62
import org.gvsig.tools.visitor.Visitor;
63
import org.slf4j.Logger;
64
import org.slf4j.LoggerFactory;
65

    
66
@SuppressWarnings("UseSpecificCatch")
67
public class JDBCHelperBase extends AbstractDisposable implements ResourceConsumer, JDBCHelper {
68

    
69
  private static final boolean ALLOW_AUTOMATIC_VALUES = true;
70
  private static final String QUOTE_FOR_USE_IN_IDENTIFIERS = "\"";
71
  private static final String QUOTE_FOR_USE_IN_STRINGS = "'";
72

    
73
  private static final Logger LOGGER = LoggerFactory.getLogger(JDBCHelperBase.class);
74

    
75
  private ResulSetControler resulSetControler = null;
76

    
77
  // Quien ha creado este helper.
78
  // Se le reenviaran las notificaciones del recurso asociado al helper.
79
  private ResourceConsumer helperClient = null;
80

    
81
  private GeometryManager geometryManager = null;
82

    
83
  private JDBCConnectionParameters connectionParameters;
84

    
85
  private JDBCStoreProvider store;
86

    
87
  protected OperationsFactory operationsFactory = null;
88

    
89
  protected SRSSolver srssolver;
90

    
91
  public JDBCHelperBase(JDBCConnectionParameters connectionParameters) {
92
    this.connectionParameters = connectionParameters;
93

    
94
    // If a particular treatment is required to convert SRS to the 
95
    // BBDD format, overwrite JDBCSRSsBase and use it instead of JDBCSRSsDumb.
96
    this.srssolver = new SRSSolverDumb(this);
97
  }
98

    
99
  protected void initialize(
100
          ResourceConsumer helperClient,
101
          JDBCConnectionParameters connectionParameters,
102
          JDBCStoreProvider store
103
  ) {
104
    this.store = store;
105
    this.helperClient = helperClient;
106
    this.connectionParameters = connectionParameters;
107
    initializeResource(connectionParameters);
108
  }
109

    
110
  protected String getResourceType() {
111
    return JDBCResource.NAME;
112
  }
113

    
114
  @Override
115
  public String getProviderName() {
116
    return JDBCLibrary.NAME;
117
  }
118

    
119
  @Override
120
  public GeometrySupportType getGeometrySupportType() {
121
    // El proveedor generico de JDBC guadara las geoemtrias como WKT
122
    return GeometrySupportType.WKT;
123
  }
124

    
125
  @Override
126
  public boolean hasSpatialFunctions() {
127
    // Por defecto el proveedor generico de JDBC asume que la BBDD no 
128
    // tiene soporte para funciones espaciales.
129
    return false;
130
  }
131

    
132
  @Override
133
  public boolean allowNestedOperations() {
134
    return false;
135
  }
136

    
137
  @Override
138
  public boolean canWriteGeometry(int geometryType, int geometrySubtype) {
139
    // Como va a guardar las geometrias en WKT, puede escribirlas todas.
140
    return true;
141
  }
142

    
143
  @Override
144
  public JDBCSQLBuilderBase createSQLBuilder() {
145
    return new JDBCSQLBuilderBase(this);
146
  }
147

    
148
  @Override
149
  public String getQuoteForIdentifiers() {
150
    return QUOTE_FOR_USE_IN_IDENTIFIERS;
151
  }
152

    
153
  @Override
154
  public boolean allowAutomaticValues() {
155
    return ALLOW_AUTOMATIC_VALUES;
156
  }
157

    
158
  @Override
159
  public boolean supportOffsetInSelect() {
160
    // Asumimos que la BBDD soporta OFFSET
161
    return true;
162
  }
163

    
164
  @Override
165
  public String getQuoteForStrings() {
166
    return QUOTE_FOR_USE_IN_STRINGS;
167
  }
168

    
169
  protected boolean supportCaller(Code.Caller caller) {
170
    if (StringUtils.equalsIgnoreCase(caller.name(), "FOREING_VALUE")) {
171
      return true;
172
    }
173
    Function function = caller.function();
174
    if (function == null) {
175
      return false;
176
    }
177
    if (!function.isSQLCompatible()) {
178
      return false;
179
    }
180
    if (!this.hasSpatialFunctions()) {
181
      if (StringUtils.equalsIgnoreCase(function.group(), Function.GROUP_OGC)) {
182
        return false;
183
      }
184
    }
185
    return true;
186
  }
187

    
188
  @Override
189
  public boolean supportFilter(final FeatureType type, Evaluator filter) {
190
    // No podemos filtrar cuando:
191
    // - Hay una subquery especificada en los parametros 
192
    // - Si hay un filtro y el getSQL devuelbe null.
193
    // - Si se esta usando alguna funcion no-sql en el getSQL
194
    // - Si se estan usando funciones OGC y no soportamos funciones espaciales
195
    // - Si se esta usando algun campo calculado en la expresion de filtro.
196
    // 
197
    // Un proveedor especifico podria sobreescribir el metodo,
198
    // para hilar mas fino al comprobar si soporta el filtro o no.
199
    //
200

    
201
    if (this.useSubquery()) {
202
      // Se esta usando una subquery en los parametros de acceso a la
203
      // BBDD, asi que no podemos filtrar.
204
      return false;
205
    }
206
    if (filter == null) {
207
      // No hay que filtrar nada, asi que se p?ede.
208
      return true;
209
    }
210
    String sql = filter.getSQL();
211
    if (StringUtils.isEmpty(sql)) {
212
      // Hay un filtro, pero la SQL es null, con lo que no podemos
213
      // filtrar por el.
214
      return false;
215
    }
216

    
217
    // Ahora vamos a comprobar que las funciones que se usan son 
218
    // compatibles sql, y que no se usan funciones OGC si el 
219
    // proveedor dice que no soporta funciones espaciales.
220
    // Tambien comprobaremos que el filtro no usa ningun campo calculado.
221
    final MutableBoolean isCompatible = new MutableBoolean(true);
222
    ExpressionEvaluatorManager manager = ExpressionEvaluatorLocator.getManager();
223
    Code code = manager.compile(sql);
224
    SymbolTable symbolTable = manager.createSymbolTable();
225
    code.link(symbolTable);
226
    try {
227
      code.accept(new Visitor() {
228
        @Override
229
        public void visit(Object code_obj) throws VisitCanceledException, BaseException {
230
          Code code = (Code) code_obj;
231
          switch (code.code()) {
232
            case Code.CALLER:
233
              Code.Caller caller = (Code.Caller) code;
234
              if (!supportCaller(caller)) {
235
                isCompatible.setValue(false);
236
                throw new VisitCanceledException();
237
              }
238
              break;
239

    
240
            case Code.IDENTIFIER:
241
              Code.Identifier identifier = (Code.Identifier) code;
242
              if (type != null) {
243
                FeatureAttributeDescriptor attrdesc = type.getAttributeDescriptor(identifier.name());
244
                if (attrdesc != null) {
245
                  if (attrdesc.isComputed()) {
246
                    isCompatible.setValue(false);
247
                    throw new VisitCanceledException();
248
                  }
249
                }
250
              }
251
              break;
252
          }
253
        }
254
      });
255

    
256
    } catch (VisitCanceledException ex) {
257
      // Do nothing
258
    } catch (Exception ex) {
259
      LOGGER.warn("Can't calculate if is SQL compatible.", ex);
260
    }
261

    
262
    return isCompatible.booleanValue();
263
  }
264

    
265
  @Override
266
  public boolean supportOrder(FeatureType type, FeatureQueryOrder order) {
267
    if (this.useSubquery()) {
268
      return false;
269
    }
270
    if (order == null) {
271
      return true;
272
    }
273
    for (FeatureQueryOrder.FeatureQueryOrderMember member : order.members()) {
274
      if (member.hasEvaluator()) {
275
        if (!this.supportFilter(type, member.getEvaluator())) {
276
          return false;
277
        }
278
      }
279
    }
280
    return true;
281
  }
282

    
283
  @Override
284
  public OperationsFactory getOperations() {
285
    if (this.operationsFactory == null) {
286
      this.operationsFactory = new OperationsFactoryBase(this);
287
    }
288
    return operationsFactory;
289
  }
290

    
291
  protected void initializeResource(JDBCConnectionParameters params) {
292
//        Object[] resourceParams = new Object[]{
293
//            params.getUrl(),
294
//            params.getHost(),
295
//            params.getPort(),
296
//            params.getDBName(),
297
//            params.getUser(),
298
//            params.getPassword(),
299
//            params.getJDBCDriverClassName()
300
//        };
301
//
302
//        try {
303
//            ResourceManagerProviderServices manager
304
//                    = (ResourceManagerProviderServices) DALLocator.getResourceManager();
305
//            JDBCResource resource = (JDBCResource) manager.createAddResource(
306
//                    this.getResourceType(),
307
//                    resourceParams
308
//            );
309
//            this.resource = resource;
310
//            this.resource.addConsumer(this);
311
//        } catch (InitializeException ex) {
312
//            logger.trace("Can't initialize resource (" + ArrayUtils.toString(resourceParams) + ").", ex);
313
//            throw new RuntimeException("Can't initialize resource (" + ArrayUtils.toString(resourceParams) + ").", ex);
314
//        }
315

    
316
  }
317

    
318
  @Override
319
  public String getSourceId() {
320
    return this.store.getSourceId();
321
  }
322

    
323
  @Override
324
  public JDBCResource getResource() {
325
    return null;
326
//        return this.resource;
327
  }
328

    
329
  @Override
330
  public Connection getConnection() throws AccessResourceException {
331
    throw new NotYetImplemented();
332
  }
333

    
334
  @Override
335
  public Connection getConnectionWritable() throws AccessResourceException {
336
    return this.getConnection();
337
  }
338

    
339
  @Override
340
  public String getConnectionURL() {
341
    return null;
342
  }
343

    
344
  @Override
345
  public JDBCConnectionParameters getConnectionParameters() {
346
    return connectionParameters;
347
  }
348

    
349
  @Override
350
  public void closeConnection(Connection connection) {
351
    if (connection != null) {
352
      LOGGER.trace("Clossing connection " + connection.hashCode());
353
      try {
354
        connection.close();
355
      } catch (Exception ex) {
356
        LOGGER.warn("Can't close connection.", ex);
357
      }
358
    }
359
  }
360

    
361
  @Override
362
  public void closeConnectionQuietly(Connection connection) {
363
    if (connection != null) {
364
      LOGGER.trace("Clossing connection quietly " + connection.hashCode());
365
      try {
366
        connection.close();
367
      } catch (Exception ex) {
368
        LOGGER.warn("Can't close connection.", ex);
369
      }
370
    }
371
  }
372

    
373
  @Override
374
  protected void doDispose() throws BaseException {
375
    JDBCUtils.closeQuietly(this);
376
  }
377

    
378
  @Override
379
  public void close() throws Exception {
380
//        this.resource.removeConsumer(this);
381
    JDBCUtils.closeQuietly(this.resulSetControler);
382
//        this.resource = null;
383
    this.resulSetControler = null;
384
  }
385

    
386
  @Override
387
  public boolean closeResourceRequested(ResourceProvider resource) {
388
    return this.helperClient.closeResourceRequested(resource);
389
  }
390

    
391
  @Override
392
  public void resourceChanged(ResourceProvider resource) {
393
    this.helperClient.resourceChanged(resource);
394
  }
395

    
396
  @Override
397
  public GeometryManager getGeometryManager() {
398
    if (this.geometryManager == null) {
399
      this.geometryManager = GeometryLocator.getGeometryManager();
400
    }
401
    return this.geometryManager;
402
  }
403

    
404
  @Override
405
  public ResulSetControler getResulSetControler() {
406
    if (this.resulSetControler == null) {
407
      this.resulSetControler = new ResulSetControlerBase(this);
408
    }
409
    return this.resulSetControler;
410
  }
411

    
412
  @Override
413
  public void fetchFeature(FeatureProvider feature, ResultSetEntry rs) throws DataException {
414
    fetchFeature(feature, rs.get(), rs.getColumns(), rs.getExtraValueNames());
415
  }
416

    
417
  @Override
418
  public void fetchFeature(FeatureProvider feature, ResultSet rs, FeatureAttributeDescriptor[] columns, String[] extraValueNames) throws DataException {
419
    Object value;
420
    try {
421
      int rsIndex = 1;
422
      for (FeatureAttributeDescriptor column : columns) {
423
        switch (column.getType()) {
424
          case DataTypes.GEOMETRY:
425
            value = this.getGeometryFromColumn(rs, rsIndex++);
426
            break;
427
          default:
428
            value = rs.getObject(rsIndex++);
429
            if (value instanceof Blob) {
430
              Blob blob = (Blob) value;
431
              value = blob.getBytes(0, (int) blob.length());
432
              blob.free();
433
            }
434
        }
435
        feature.set(column.getIndex(), value);
436
      }
437
      if (ArrayUtils.isNotEmpty(extraValueNames)) {
438
        feature.setExtraValueNames(extraValueNames);
439
        for (int index = 0; index < extraValueNames.length; index++) {
440
          value = rs.getObject(rsIndex++);
441
          if (value instanceof Blob) {
442
            Blob blob = (Blob) value;
443
            value = blob.getBytes(0, (int) blob.length());
444
            blob.free();
445
          }
446
          feature.setExtraValue(index, value);
447
        }
448
      }
449
    } catch (Exception ex) {
450
      throw new JDBCCantFetchValueException(ex);
451
    }
452
  }
453

    
454
  @Override
455
  public Geometry getGeometryFromColumn(ResultSetEntry rs, int index) throws DataException {
456
    return getGeometryFromColumn(rs.get(), index);
457
  }
458

    
459
  @Override
460
  public Geometry getGeometryFromColumn(ResultSet rs, int index) throws DataException {
461
    try {
462
      Object value;
463
      switch (this.getGeometrySupportType()) {
464
        case NATIVE:
465
        case WKB:
466
          value = rs.getBytes(index);
467
          if (value == null) {
468
            return null;
469
          }
470
          return this.getGeometryManager().createFrom((byte[]) value);
471

    
472
        case EWKB:
473
          value = rs.getBytes(index);
474
          if (value == null) {
475
            return null;
476
          }
477
          return this.getGeometryManager().createFrom((byte[]) value);
478
        case WKT:
479
        default:
480
          value = rs.getString(index);
481
          if (value == null) {
482
            return null;
483
          }
484
          return this.getGeometryManager().createFrom((String) value);
485

    
486
      }
487
    } catch (Exception ex) {
488
      throw new JDBCCantFetchValueException(ex);
489
    }
490
  }
491

    
492
  @Override
493
  public FeatureProvider createFeature(FeatureType featureType) throws DataException {
494
    return this.store.getStoreServices().createDefaultFeatureProvider(featureType);
495
  }
496

    
497
  @Override
498
  public boolean useSubquery() {
499
    if (this.store == null) {
500
      return false;
501
    }
502
    return !StringUtils.isEmpty(this.store.getParameters().getSQL());
503
  }
504

    
505
  @Override
506
  public SRSSolver getSRSSolver() {
507
    return this.srssolver;
508
  }
509

    
510
  @Override
511
  public JDBCStoreProvider createProvider(
512
          JDBCStoreParameters parameters,
513
          DataStoreProviderServices providerServices
514
  ) throws InitializeException {
515

    
516
    JDBCStoreProviderBase theStore = new JDBCStoreProviderBase(
517
            parameters,
518
            providerServices,
519
            DBHelper.newMetadataContainer(JDBCLibrary.NAME),
520
            this
521
    );
522
    this.initialize(theStore, parameters, theStore);
523
    return theStore;
524
  }
525

    
526
  @Override
527
  public JDBCServerExplorer createServerExplorer(
528
          JDBCServerExplorerParameters parameters,
529
          DataServerExplorerProviderServices providerServices
530
  ) throws InitializeException {
531

    
532
    JDBCServerExplorer explorer = new JDBCServerExplorerBase(
533
            parameters,
534
            providerServices,
535
            this
536
    );
537
    this.initialize(explorer, parameters, null);
538
    return explorer;
539
  }
540

    
541
  @Override
542
  public JDBCNewStoreParameters createNewStoreParameters() {
543
    return new JDBCNewStoreParameters();
544
  }
545

    
546
  @Override
547
  public JDBCStoreParameters createOpenStoreParameters() {
548
    return new JDBCStoreParameters();
549
  }
550

    
551
  @Override
552
  public JDBCServerExplorerParameters createServerExplorerParameters() {
553
    return new JDBCServerExplorerParameters();
554
  }
555

    
556
  @Override
557
  public String getSourceId(JDBCStoreParameters parameters) {
558
    return parameters.getHost() + ":"
559
            + parameters.getDBName() + ":"
560
            + parameters.getSchema() + ":"
561
            + parameters.tableID();
562
  }
563

    
564
  @Override
565
  public boolean isThreadSafe() {
566
    return true;
567
  }
568

    
569
  @Override
570
  public void processSpecialFunctions(
571
          SQLBuilder sqlbuilder,
572
          FeatureType type,
573
          List<String> extra_column_names) {
574
    replaceForeingValueFunction(sqlbuilder, type, extra_column_names);
575
    replaceExistsFunction(sqlbuilder, type, extra_column_names);
576
  }
577

    
578
  private void replaceExistsFunction(
579
          SQLBuilder sqlbuilder,
580
          FeatureType type,
581
          final List<String> extra_column_names) {
582
    
583
    //  Si lse encuentra una construccion del tipo:
584
    //    SELECT ... FROM ... WHERE ... EXISTS(list, 'EXISTS_ID') ...
585
    //  se traslada a:
586
    //    SELECT ... EXISTS(list) AS EXISTS_ID FROM ... WHERE ... EXISTS(list) ...
587
    //  Y se a?ade el valor ESISTS_ID a las columnas extra a recuperar de la consulta.
588
    //          
589
    
590
    final SQLBuilder.SelectBuilder select = sqlbuilder.select();
591
    final ExpressionBuilder where = select.where();
592
    if (where == null || where.isEmpty()) {
593
      return;
594
    }
595
    final List<ExpressionBuilder.Value[]> value_replacements = new ArrayList<>();
596
    where.accept(new ExpressionBuilder.Visitor() {
597
      @Override
598
      public void visit(ExpressionBuilder.Visitable value) {
599
        if (!(value instanceof ExpressionBuilder.Function)) {
600
          return;
601
        }
602
        ExpressionBuilder.Function function = (ExpressionBuilder.Function) value;
603
        if (!StringUtils.equalsIgnoreCase(function.name(), FUNCTION_EXISTS)) {
604
          return;
605
        }
606
        if (function.parameters().size() != 2) {
607
          return;
608
        }
609
        ExpressionBuilder.Value arg0 = function.parameters().get(0);
610
        ExpressionBuilder.Value arg1 = function.parameters().get(1);
611
        if (arg1 == null) {
612
          return;
613
        }
614
        String columnName = (String) ((ExpressionBuilder.Constant) arg1).value();
615
        SQLBuilder.SelectColumnBuilder column = select.column();
616
        column.value(
617
                sqlbuilder.expression().function(FUNCTION_EXISTS, arg0)
618
        );
619
        column.as(columnName);
620
//        
621
//        value_replacements.add(
622
//                new ExpressionBuilder.Value[]{
623
//                  function,
624
//                  sqlbuilder.column(columnName)
625
//                }
626
//        );
627
        if( extra_column_names!=null ) {
628
          extra_column_names.add(columnName);
629
        }
630
      }
631
    }, null);
632
    if (value_replacements.isEmpty()) {
633
      return;
634
    }
635
    // Realizamos los reemplazos calculados previamente (value_replacements).
636
    for (ExpressionBuilder.Value[] replaceValue : value_replacements) {
637
      ExpressionBuilder.Value target = replaceValue[0];
638
      ExpressionBuilder.Value replacement = replaceValue[1];
639
      sqlbuilder.select().replace(target, replacement);
640
    }
641
  }
642

    
643
  private void replaceForeingValueFunction(
644
          SQLBuilder sqlbuilder,
645
          FeatureType type,
646
          List<String> extra_column_names) {
647
    try {
648
      // See test SQLBuilderTest->testForeingValue()
649
      final ExpressionBuilder where = sqlbuilder.select().where();
650
      if (where == null || where.isEmpty()) {
651
        return;
652
      }
653
      final SQLBuilder.TableNameBuilder table = sqlbuilder.select().from().table();
654
      final ExpressionBuilder expbuilder = sqlbuilder.expression();
655

    
656
      final List<String> foreing_value_args;
657
      if (extra_column_names == null) {
658
        foreing_value_args = new ArrayList<>();
659
      } else {
660
        foreing_value_args = extra_column_names;
661
      }
662
      final List<ExpressionBuilder.Value[]> value_replacements = new ArrayList<>();
663

    
664
      // Buscamos las llamadas a la funcion "foreing_value" y nos quedamos
665
      // el argumento de esta asi como por que tendriamos que sustituirla 
666
      // una vez hechos los left joins que toquen.
667
      where.accept(new ExpressionBuilder.Visitor() {
668
        @Override
669
        public void visit(ExpressionBuilder.Visitable value) {
670
          // Requiere que sea la funcion "FOREING_VALUE con un solo 
671
          // argumento que sea una constante de tipo string.
672
          if (!(value instanceof ExpressionBuilder.Function)) {
673
            return;
674
          }
675
          ExpressionBuilder.Function function = (ExpressionBuilder.Function) value;
676
          if (!StringUtils.equalsIgnoreCase(function.name(), FUNCTION_FOREING_VALUE)) {
677
            return;
678
          }
679
          if (function.parameters().size() != 1) {
680
            return;
681
          }
682
          ExpressionBuilder.Value arg = function.parameters().get(0);
683
          if (!(arg instanceof ExpressionBuilder.Constant)) {
684
            return;
685
          }
686
          Object arg_value = ((ExpressionBuilder.Constant) arg).value();
687
          if (!(arg_value instanceof CharSequence)) {
688
            return;
689
          }
690
          String foreing_value_arg = arg_value.toString();
691
          String[] foreingNameParts = StringUtils.split(foreing_value_arg, "[.]");
692
          if (foreingNameParts.length != 2) {
693
            // De momento solo tratamos joins entre dos tablas.
694
            return;
695
          }
696
          String columnNameLocal = foreingNameParts[0];
697
          String columnNameForeing = foreingNameParts[1];
698
          FeatureAttributeDescriptor attr = type.getAttributeDescriptor(columnNameLocal);
699
          if (!attr.isForeingKey()) {
700
            // Uhm... si el argumento no referencia a un campo que es
701
            // clave ajena no lo procesamos. 
702
            // ? Deberiamos lanzar un error ?
703
            return;
704
          }
705
          // Nos guardaremos por que hay que reemplazar la funcion 
706
          // FOREING_VALUE, y su argumento para mas tarde.
707
          ForeingKey foreingKey = attr.getForeingKey();
708
          SQLBuilder.TableNameBuilder foreingTable = sqlbuilder.createTableNameBuilder()
709
                  .database(table.getDatabase())
710
                  .schema(table.getSchema())
711
                  .name(foreingKey.getTableName());
712
          // Reemplzaremos la funcion FOREING_VALUE, por el acceso al campo
713
          // que toca de la tabla a la que referencia la clave ajena.
714
          ExpressionBuilder.Variable function_replacement = sqlbuilder.column(foreingTable, columnNameForeing);
715
          value_replacements.add(
716
                  new ExpressionBuilder.Value[]{
717
                    function,
718
                    function_replacement
719
                  }
720
          );
721
          foreing_value_args.add(foreing_value_arg);
722
        }
723
      }, null);
724

    
725
      // Si no habia ningun llamada a la funcion FOREING_VALUE, no hay que
726
      // hacer nada.
727
      if (foreing_value_args.isEmpty()) {
728
        return;
729
      }
730

    
731
      // Calculamos que referencias de columnas hemos de cambiar para 
732
      // que no aparezcan ambiguedades al hacer el join (nombres de
733
      // columna de una y otra tabla que coincidan).
734
      // Para las columnas que puedan dar conflicto se prepara un reemplazo 
735
      // de estas que tenga el nombre de tabla.
736
      for (ExpressionBuilder.Variable variable : sqlbuilder.variables()) {
737
        if (variable instanceof SQLBuilderBase.ColumnBase) {
738
          continue;
739
        }
740
        for (String foreingName : foreing_value_args) {
741
          String[] foreingNameParts = foreingName.split("[.]");
742
          if (foreingNameParts.length != 2) {
743
            continue;
744
          }
745
          String columnNameLocal = foreingNameParts[0];
746
          String columnNameForeing = foreingNameParts[1];
747
          if (StringUtils.equalsIgnoreCase(variable.name(), columnNameForeing)
748
                  || StringUtils.equalsIgnoreCase(variable.name(), columnNameLocal)) {
749
            ExpressionBuilder.Variable variable_replacement = sqlbuilder.column(
750
                    table,
751
                    variable.name()
752
            );
753
            value_replacements.add(
754
                    new ExpressionBuilder.Value[]{
755
                      variable,
756
                      variable_replacement
757
                    }
758
            );
759
          }
760
        }
761
      }
762

    
763
      // Realizamos los reemplazos calculados previamente (value_replacements).
764
      for (ExpressionBuilder.Value[] replaceValue : value_replacements) {
765
        ExpressionBuilder.Value target = replaceValue[0];
766
        ExpressionBuilder.Value replacement = replaceValue[1];
767
        sqlbuilder.select().replace(target, replacement);
768
      }
769

    
770
      // A?adimos a la SQL los "LEFT JOIN" que toca para poder acceder
771
      // a los valores referenciados por la funcion FOREING_VALUE.
772
      // Ademas a?adimos los valores referenciados por la funcion FOREING_VALUE
773
      // como columnas de la SQL para poder acceder a ellos si fuese necesario.
774
      for (String foreingName : foreing_value_args) {
775
        String[] foreingNameParts = foreingName.split("[.]");
776
        if (foreingNameParts.length != 2) {
777
          continue;
778
        }
779
        String columnNameLocal = foreingNameParts[0];
780
        String columnNameForeing = foreingNameParts[1];
781
        FeatureAttributeDescriptor attr = type.getAttributeDescriptor(columnNameLocal);
782
        if (attr.isForeingKey()) {
783
          ForeingKey foreingKey = attr.getForeingKey();
784
          SQLBuilder.TableNameBuilder foreingTable = sqlbuilder.createTableNameBuilder()
785
                  .database(table.getDatabase())
786
                  .schema(table.getSchema())
787
                  .name(foreingKey.getTableName());
788
          SQLBuilder.TableNameBuilder mainTable = sqlbuilder.createTableNameBuilder()
789
                  .database(table.getDatabase())
790
                  .schema(table.getSchema())
791
                  .name(table.getName());
792

    
793
          sqlbuilder.select().from()
794
                  .left_join(
795
                          foreingTable,
796
                          expbuilder.eq(
797
                                  sqlbuilder.column(mainTable, columnNameLocal),
798
                                  sqlbuilder.column(foreingTable, foreingKey.getCodeName())
799
                          )
800
                  );
801
          sqlbuilder.select().column().name(foreingTable, columnNameForeing);
802
        }
803
      }
804

    
805
    } catch (Throwable th) {
806
      LOGGER.warn("Can't replace FORENG_VALUE function.", th);
807
      throw th;
808
    } finally {
809
      LOGGER.trace("Exit from replaceForeingValueFunction.");
810
    }
811
  }
812
}