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

History | View | Annotate | Download (28.1 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.sql.Blob;
27
import java.sql.Clob;
28
import org.gvsig.fmap.dal.store.jdbc2.impl.ResulSetControlerBase;
29
import org.gvsig.fmap.dal.store.jdbc2.spi.operations.OperationsFactoryBase;
30
import java.sql.Connection;
31
import java.sql.ResultSet;
32
import java.util.ArrayList;
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

    
56
import org.gvsig.fmap.dal.feature.spi.FeatureProvider;
57
import org.gvsig.fmap.dal.feature.spi.SQLBuilderBase;
58
import org.gvsig.fmap.dal.resource.exception.AccessResourceException;
59
import org.gvsig.fmap.dal.resource.spi.ResourceConsumer;
60
import org.gvsig.fmap.dal.resource.spi.ResourceProvider;
61
import org.gvsig.fmap.dal.spi.DataServerExplorerProviderServices;
62
import org.gvsig.fmap.dal.spi.DataStoreProviderServices;
63
import org.gvsig.fmap.dal.store.db.DBHelper;
64
import org.gvsig.fmap.dal.store.jdbc.JDBCConnectionParameters;
65
import org.gvsig.fmap.dal.store.jdbc.JDBCLibrary;
66
import org.gvsig.fmap.dal.store.jdbc.JDBCNewStoreParameters;
67
import org.gvsig.fmap.dal.store.jdbc.JDBCResource;
68
import org.gvsig.fmap.dal.store.jdbc.JDBCServerExplorerParameters;
69
import org.gvsig.fmap.dal.store.jdbc.JDBCStoreParameters;
70
import org.gvsig.fmap.dal.store.jdbc.exception.JDBCCantFetchValueException;
71
import org.gvsig.fmap.dal.store.jdbc2.JDBCHelper;
72
import org.gvsig.fmap.dal.store.jdbc2.JDBCServerExplorer;
73
import org.gvsig.fmap.dal.store.jdbc2.JDBCStoreProvider;
74
import org.gvsig.fmap.dal.store.jdbc2.JDBCUtils;
75
import org.gvsig.fmap.dal.store.jdbc2.OperationsFactory;
76
import org.gvsig.fmap.dal.store.jdbc2.ResulSetControler;
77
import org.gvsig.fmap.dal.store.jdbc2.ResulSetControler.ResultSetEntry;
78
import org.gvsig.fmap.geom.Geometry;
79
import org.gvsig.fmap.geom.GeometryLocator;
80
import org.gvsig.fmap.geom.GeometryManager;
81
import org.gvsig.tools.dispose.impl.AbstractDisposable;
82
import org.gvsig.tools.evaluator.Evaluator;
83
import org.gvsig.tools.exception.BaseException;
84
import org.gvsig.tools.exception.NotYetImplemented;
85
import org.gvsig.tools.visitor.VisitCanceledException;
86
import org.gvsig.tools.visitor.Visitor;
87
import org.slf4j.Logger;
88
import org.slf4j.LoggerFactory;
89

    
90
@SuppressWarnings("UseSpecificCatch")
91
public class JDBCHelperBase extends AbstractDisposable implements ResourceConsumer, JDBCHelper {
92

    
93
  private static final boolean ALLOW_AUTOMATIC_VALUES = true;
94
  private static final String QUOTE_FOR_USE_IN_IDENTIFIERS = "\"";
95
  private static final String QUOTE_FOR_USE_IN_STRINGS = "'";
96

    
97
  private static final Logger LOGGER = LoggerFactory.getLogger(JDBCHelperBase.class);
98

    
99
  private ResulSetControler resulSetControler = null;
100

    
101
  // Quien ha creado este helper.
102
  // Se le reenviaran las notificaciones del recurso asociado al helper.
103
  private ResourceConsumer helperClient = null;
104

    
105
  private GeometryManager geometryManager = null;
106

    
107
  private JDBCConnectionParameters connectionParameters;
108

    
109
  private JDBCStoreProvider store;
110

    
111
  protected OperationsFactory operationsFactory = null;
112

    
113
  protected SRSSolver srssolver;
114

    
115
  public JDBCHelperBase(JDBCConnectionParameters connectionParameters) {
116
    this.connectionParameters = connectionParameters;
117

    
118
    // If a particular treatment is required to convert SRS to the 
119
    // BBDD format, overwrite JDBCSRSsBase and use it instead of JDBCSRSsDumb.
120
    this.srssolver = new SRSSolverDumb(this);
121
  }
122

    
123
  protected void initialize(
124
          ResourceConsumer helperClient,
125
          JDBCConnectionParameters connectionParameters,
126
          JDBCStoreProvider store
127
  ) {
128
    this.store = store;
129
    this.helperClient = helperClient;
130
    this.connectionParameters = connectionParameters;
131
    initializeResource(connectionParameters);
132
  }
133

    
134
  protected String getResourceType() {
135
    return JDBCResource.NAME;
136
  }
137

    
138
  @Override
139
  public String getProviderName() {
140
    return JDBCLibrary.NAME;
141
  }
142

    
143
  @Override
144
  public GeometrySupportType getGeometrySupportType() {
145
    // El proveedor generico de JDBC guadara las geoemtrias como WKT
146
    return GeometrySupportType.WKT;
147
  }
148

    
149
  @Override
150
  public boolean hasSpatialFunctions() {
151
    // Por defecto el proveedor generico de JDBC asume que la BBDD no 
152
    // tiene soporte para funciones espaciales.
153
    return false;
154
  }
155

    
156
  @Override
157
  public boolean allowNestedOperations() {
158
    return false;
159
  }
160

    
161
  @Override
162
  public boolean canWriteGeometry(int geometryType, int geometrySubtype) {
163
    // Como va a guardar las geometrias en WKT, puede escribirlas todas.
164
    return true;
165
  }
166

    
167
  @Override
168
  public JDBCSQLBuilderBase createSQLBuilder() {
169
    return new JDBCSQLBuilderBase(this);
170
  }
171

    
172
  @Override
173
  public String getQuoteForIdentifiers() {
174
    return QUOTE_FOR_USE_IN_IDENTIFIERS;
175
  }
176

    
177
  @Override
178
  public boolean allowAutomaticValues() {
179
    return ALLOW_AUTOMATIC_VALUES;
180
  }
181

    
182
  @Override
183
  public boolean supportOffsetInSelect() {
184
    // Asumimos que la BBDD soporta OFFSET
185
    return true;
186
  }
187

    
188
  @Override
189
  public String getQuoteForStrings() {
190
    return QUOTE_FOR_USE_IN_STRINGS;
191
  }
192

    
193
  protected boolean supportCaller(Code.Callable caller) {
194
    if (StringUtils.equalsIgnoreCase(caller.name(), "FOREING_VALUE")) {
195
      return true;
196
    }
197
    Function function = caller.function();
198
    if (function == null) {
199
      return false;
200
    }
201
    if (!function.isSQLCompatible()) {
202
      return false;
203
    }
204
    if (!this.hasSpatialFunctions()) {
205
      if (StringUtils.equalsIgnoreCase(function.group(), Function.GROUP_OGC)) {
206
        return false;
207
      }
208
    }
209
    return true;
210
  }
211

    
212
  @Override
213
  public boolean supportFilter(final FeatureType type, Evaluator filter) {
214
    // No podemos filtrar cuando:
215
    // - Hay una subquery especificada en los parametros 
216
    // - Si hay un filtro y el getSQL devuelbe null.
217
    // - Si se esta usando alguna funcion no-sql en el getSQL
218
    // - Si se estan usando funciones OGC y no soportamos funciones espaciales
219
    // - Si se esta usando algun campo calculado en la expresion de filtro.
220
    // 
221
    // Un proveedor especifico podria sobreescribir el metodo,
222
    // para hilar mas fino al comprobar si soporta el filtro o no.
223
    //
224

    
225
    if (this.useSubquery()) {
226
      // Se esta usando una subquery en los parametros de acceso a la
227
      // BBDD, asi que no podemos filtrar.
228
      return false;
229
    }
230
    if (filter == null) {
231
      // No hay que filtrar nada, asi que se p?ede.
232
      return true;
233
    }
234
    String sql = filter.getSQL();
235
    if (StringUtils.isEmpty(sql)) {
236
      // Hay un filtro, pero la SQL es null, con lo que no podemos
237
      // filtrar por el.
238
      return false;
239
    }
240

    
241
    // Ahora vamos a comprobar que las funciones que se usan son 
242
    // compatibles sql, y que no se usan funciones OGC si el 
243
    // proveedor dice que no soporta funciones espaciales.
244
    // Tambien comprobaremos que el filtro no usa ningun campo calculado.
245
    final MutableBoolean isCompatible = new MutableBoolean(true);
246
    ExpressionEvaluatorManager manager = ExpressionEvaluatorLocator.getManager();
247
    Code code = manager.compile(sql);
248
    SymbolTable symbolTable = manager.createSymbolTable();
249
    code.link(symbolTable);
250
    try {
251
      code.accept(new Visitor() {
252
        @Override
253
        public void visit(Object code_obj) throws VisitCanceledException, BaseException {
254
          Code code = (Code) code_obj;
255
          switch (code.code()) {
256
            case Code.CALLABLE:
257
              Code.Callable caller = (Code.Callable) code;
258
              if (!supportCaller(caller)) {
259
                isCompatible.setValue(false);
260
                throw new VisitCanceledException();
261
              }
262
              break;
263

    
264
            case Code.IDENTIFIER:
265
              Code.Identifier identifier = (Code.Identifier) code;
266
              if (type != null) {
267
                FeatureAttributeDescriptor attrdesc = type.getAttributeDescriptor(identifier.name());
268
                if (attrdesc != null) {
269
                  if (attrdesc.isComputed()) {
270
                    isCompatible.setValue(false);
271
                    throw new VisitCanceledException();
272
                  }
273
                }
274
              }
275
              break;
276
          }
277
        }
278
      });
279

    
280
    } catch (VisitCanceledException ex) {
281
      // Do nothing
282
    } catch (Exception ex) {
283
      LOGGER.warn("Can't calculate if is SQL compatible.", ex);
284
    }
285

    
286
    return isCompatible.booleanValue();
287
  }
288

    
289
  @Override
290
  public boolean supportOrder(FeatureType type, FeatureQueryOrder order) {
291
    if (this.useSubquery()) {
292
      return false;
293
    }
294
    if (order == null) {
295
      return true;
296
    }
297
    for (FeatureQueryOrder.FeatureQueryOrderMember member : order.members()) {
298
      if (member.hasEvaluator()) {
299
        if (!this.supportFilter(type, member.getEvaluator())) {
300
          return false;
301
        }
302
      }
303
    }
304
    return true;
305
  }
306

    
307
  @Override
308
  public OperationsFactory getOperations() {
309
    if (this.operationsFactory == null) {
310
      this.operationsFactory = new OperationsFactoryBase(this);
311
    }
312
    return operationsFactory;
313
  }
314

    
315
  protected void initializeResource(JDBCConnectionParameters params) {
316
//        Object[] resourceParams = new Object[]{
317
//            params.getUrl(),
318
//            params.getHost(),
319
//            params.getPort(),
320
//            params.getDBName(),
321
//            params.getUser(),
322
//            params.getPassword(),
323
//            params.getJDBCDriverClassName()
324
//        };
325
//
326
//        try {
327
//            ResourceManagerProviderServices manager
328
//                    = (ResourceManagerProviderServices) DALLocator.getResourceManager();
329
//            JDBCResource resource = (JDBCResource) manager.createAddResource(
330
//                    this.getResourceType(),
331
//                    resourceParams
332
//            );
333
//            this.resource = resource;
334
//            this.resource.addConsumer(this);
335
//        } catch (InitializeException ex) {
336
//            logger.trace("Can't initialize resource (" + ArrayUtils.toString(resourceParams) + ").", ex);
337
//            throw new RuntimeException("Can't initialize resource (" + ArrayUtils.toString(resourceParams) + ").", ex);
338
//        }
339

    
340
  }
341

    
342
  @Override
343
  public String getSourceId() {
344
    return this.store.getSourceId();
345
  }
346

    
347
  @Override
348
  public JDBCResource getResource() {
349
    return null;
350
//        return this.resource;
351
  }
352

    
353
  @Override
354
  public Connection getConnection() throws AccessResourceException {
355
    throw new NotYetImplemented();
356
  }
357

    
358
  @Override
359
  public Connection getConnectionWritable() throws AccessResourceException {
360
    return this.getConnection();
361
  }
362

    
363
  @Override
364
  public String getConnectionURL() {
365
    return null;
366
  }
367

    
368
  @Override
369
  public JDBCConnectionParameters getConnectionParameters() {
370
    return connectionParameters;
371
  }
372

    
373
  @Override
374
  public void closeConnection(Connection connection) {
375
      JDBCUtils.close(connection);
376
  }
377

    
378
  @Override
379
  public void closeConnectionQuietly(Connection connection) {
380
      JDBCUtils.closeQuietly(connection);
381
  }
382

    
383
  @Override
384
  protected void doDispose() throws BaseException {
385
    JDBCUtils.closeQuietly(this);
386
  }
387

    
388
  @Override
389
  public void close() throws Exception {
390
//        this.resource.removeConsumer(this);
391
    JDBCUtils.closeQuietly(this.resulSetControler);
392
//        this.resource = null;
393
    this.resulSetControler = null;
394
  }
395

    
396
  @Override
397
  public boolean closeResourceRequested(ResourceProvider resource) {
398
    return this.helperClient.closeResourceRequested(resource);
399
  }
400

    
401
  @Override
402
  public void resourceChanged(ResourceProvider resource) {
403
    this.helperClient.resourceChanged(resource);
404
  }
405

    
406
  @Override
407
  public GeometryManager getGeometryManager() {
408
    if (this.geometryManager == null) {
409
      this.geometryManager = GeometryLocator.getGeometryManager();
410
    }
411
    return this.geometryManager;
412
  }
413

    
414
  @Override
415
  public ResulSetControler getResulSetControler() {
416
    if (this.resulSetControler == null) {
417
      this.resulSetControler = new ResulSetControlerBase(this);
418
    }
419
    return this.resulSetControler;
420
  }
421

    
422
  @Override
423
  public void fetchFeature(FeatureProvider feature, ResultSetEntry rs) throws DataException {
424
    fetchFeature(feature, rs.get(), rs.getColumns(), rs.getExtraValueNames());
425
  }
426

    
427
  @Override
428
  public void fetchFeature(FeatureProvider feature, ResultSet rs, FeatureAttributeDescriptor[] columns, String[] extraValueNames) throws DataException {
429
    Object value;
430
    try {
431
      int rsIndex = 1;
432
      for (FeatureAttributeDescriptor column : columns) {
433
        switch (column.getType()) {
434
          case DataTypes.GEOMETRY:
435
            value = this.getGeometryFromColumn(rs, rsIndex++);
436
            break;
437
          default:
438
            value = rs.getObject(rsIndex++);
439
            if (value instanceof Blob) {
440
              Blob blob = (Blob) value;
441
              value = blob.getBytes(0, (int) blob.length());
442
              blob.free();
443
            } else if(value instanceof Clob) {
444
              Clob clob = (Clob) value;
445
              value = new String(IOUtils.toCharArray(clob.getCharacterStream()));
446
              clob.free();
447
          }
448
        }
449
        feature.set(column.getIndex(), value);
450
      }
451
      if (ArrayUtils.isNotEmpty(extraValueNames)) {
452
        feature.setExtraValueNames(extraValueNames);
453
        for (int index = 0; index < extraValueNames.length; index++) {
454
          value = rs.getObject(rsIndex++);
455
          if (value instanceof Blob) {
456
            Blob blob = (Blob) value;
457
            value = blob.getBytes(0, (int) blob.length());
458
            blob.free();
459
          }
460
          feature.setExtraValue(index, value);
461
        }
462
      }
463
    } catch (Exception ex) {
464
      throw new JDBCCantFetchValueException(ex);
465
    }
466
  }
467

    
468
  @Override
469
  public Geometry getGeometryFromColumn(ResultSetEntry rs, int index) throws DataException {
470
    return getGeometryFromColumn(rs.get(), index);
471
  }
472

    
473
  @Override
474
  public Geometry getGeometryFromColumn(ResultSet rs, int index) throws DataException {
475
    try {
476
      Object value;
477
      switch (this.getGeometrySupportType()) {
478
        case NATIVE:
479
        case WKB:
480
          value = rs.getBytes(index);
481
          if (value == null) {
482
            return null;
483
          }
484
          return this.getGeometryManager().createFrom((byte[]) value);
485

    
486
        case EWKB:
487
          value = rs.getBytes(index);
488
          if (value == null) {
489
            return null;
490
          }
491
          return this.getGeometryManager().createFrom((byte[]) value);
492
        case WKT:
493
        default:
494
          value = rs.getString(index);
495
          if (value == null) {
496
            return null;
497
          }
498
          return this.getGeometryManager().createFrom((String) value);
499

    
500
      }
501
    } catch (Exception ex) {
502
      throw new JDBCCantFetchValueException(ex);
503
    }
504
  }
505

    
506
  @Override
507
  public FeatureProvider createFeature(FeatureType featureType) throws DataException {
508
    return this.store.getStoreServices().createDefaultFeatureProvider(featureType);
509
  }
510

    
511
  @Override
512
  public boolean useSubquery() {
513
    if (this.store == null) {
514
      return false;
515
    }
516
    return !StringUtils.isEmpty(this.store.getParameters().getSQL());
517
  }
518

    
519
  @Override
520
  public SRSSolver getSRSSolver() {
521
    return this.srssolver;
522
  }
523

    
524
  @Override
525
  public JDBCStoreProvider createProvider(
526
          JDBCStoreParameters parameters,
527
          DataStoreProviderServices providerServices
528
  ) throws InitializeException {
529

    
530
    JDBCStoreProviderBase theStore = new JDBCStoreProviderBase(
531
            parameters,
532
            providerServices,
533
            DBHelper.newMetadataContainer(JDBCLibrary.NAME),
534
            this
535
    );
536
    this.initialize(theStore, parameters, theStore);
537
    return theStore;
538
  }
539

    
540
  @Override
541
  public JDBCServerExplorer createServerExplorer(
542
          JDBCServerExplorerParameters parameters,
543
          DataServerExplorerProviderServices providerServices
544
  ) throws InitializeException {
545

    
546
    JDBCServerExplorer explorer = new JDBCServerExplorerBase(
547
            parameters,
548
            providerServices,
549
            this
550
    );
551
    this.initialize(explorer, parameters, null);
552
    return explorer;
553
  }
554

    
555
  @Override
556
  public JDBCNewStoreParameters createNewStoreParameters() {
557
    return new JDBCNewStoreParameters();
558
  }
559

    
560
  @Override
561
  public JDBCStoreParameters createOpenStoreParameters() {
562
    return new JDBCStoreParameters();
563
  }
564

    
565
  @Override
566
  public JDBCServerExplorerParameters createServerExplorerParameters() {
567
    return new JDBCServerExplorerParameters();
568
  }
569

    
570
  @Override
571
  public String getSourceId(JDBCStoreParameters parameters) {
572
    return parameters.getHost() + ":"
573
            + parameters.getDBName() + ":"
574
            + parameters.getSchema() + ":"
575
            + parameters.tableID();
576
  }
577

    
578
  @Override
579
  public boolean isThreadSafe() {
580
    return true;
581
  }
582

    
583
  /** 
584
   * This method has been overriden in Oracle provider
585
   * 
586
   * @param sqlbuilder
587
   * @param type
588
   * @param extra_column_names 
589
   */
590
  @Override
591
  public void processSpecialFunctions(
592
          SQLBuilder sqlbuilder,
593
          FeatureType type,
594
          List<String> extra_column_names) {
595
    replaceForeingValueFunction(sqlbuilder, type, extra_column_names);
596
    replaceExistsFunction(sqlbuilder, type, extra_column_names);
597
  }
598

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

    
642
        if( extra_column_names!=null ) {
643
          extra_column_names.add(columnName);
644
        }
645
      }
646
    }, null);
647
    if (value_replacements.isEmpty()) {
648
      return;
649
    }
650
    // Realizamos los reemplazos calculados previamente (value_replacements).
651
    for (ExpressionBuilder.Value[] replaceValue : value_replacements) {
652
      ExpressionBuilder.Value target = replaceValue[0];
653
      ExpressionBuilder.Value replacement = replaceValue[1];
654
      sqlbuilder.select().replace(target, replacement);
655
    }
656
  }
657

    
658
  protected void replaceForeingValueFunction(
659
          SQLBuilder sqlbuilder,
660
          FeatureType type,
661
          List<String> extra_column_names) {
662
    try {
663
      // See test SQLBuilderTest->testForeingValue()
664
      final ExpressionBuilder where = sqlbuilder.select().where();
665
      if (where == null || where.isEmpty()) {
666
        return;
667
      }
668
      final SQLBuilder.TableNameBuilder table = sqlbuilder.select().from().table();
669
      final ExpressionBuilder expbuilder = sqlbuilder.expression();
670

    
671
      final List<String> foreing_value_args;
672
      if (extra_column_names == null) {
673
        foreing_value_args = new ArrayList<>();
674
      } else {
675
        foreing_value_args = extra_column_names;
676
      }
677
      final List<ExpressionBuilder.Value[]> value_replacements = new ArrayList<>();
678

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

    
740
      // Si no habia ningun llamada a la funcion FOREING_VALUE, no hay que
741
      // hacer nada.
742
      if (foreing_value_args.isEmpty()) {
743
        return;
744
      }
745

    
746
      // Calculamos que referencias de columnas hemos de cambiar para 
747
      // que no aparezcan ambiguedades al hacer el join (nombres de
748
      // columna de una y otra tabla que coincidan).
749
      // Para las columnas que puedan dar conflicto se prepara un reemplazo 
750
      // de estas que tenga el nombre de tabla.
751
      for (ExpressionBuilder.Variable variable : sqlbuilder.variables()) {
752
        if (variable instanceof SQLBuilderBase.ColumnBase) {
753
          continue;
754
        }
755
        for (String foreingName : foreing_value_args) {
756
          String[] foreingNameParts = foreingName.split("[.]");
757
          if (foreingNameParts.length != 2) {
758
            continue;
759
          }
760
          String columnNameLocal = foreingNameParts[0];
761
          String columnNameForeing = foreingNameParts[1];
762
          if (StringUtils.equalsIgnoreCase(variable.name(), columnNameForeing)
763
                  || StringUtils.equalsIgnoreCase(variable.name(), columnNameLocal)) {
764
            ExpressionBuilder.Variable variable_replacement = sqlbuilder.column(
765
                    table,
766
                    variable.name()
767
            );
768
            value_replacements.add(
769
                    new ExpressionBuilder.Value[]{
770
                      variable,
771
                      variable_replacement
772
                    }
773
            );
774
          }
775
        }
776
      }
777

    
778
      // Realizamos los reemplazos calculados previamente (value_replacements).
779
      for (ExpressionBuilder.Value[] replaceValue : value_replacements) {
780
        ExpressionBuilder.Value target = replaceValue[0];
781
        ExpressionBuilder.Value replacement = replaceValue[1];
782
        sqlbuilder.select().replace(target, replacement);
783
      }
784

    
785
      // A?adimos a la SQL los "LEFT JOIN" que toca para poder acceder
786
      // a los valores referenciados por la funcion FOREING_VALUE.
787
      // Ademas a?adimos los valores referenciados por la funcion FOREING_VALUE
788
      // como columnas de la SQL para poder acceder a ellos si fuese necesario.
789
      for (String foreingName : foreing_value_args) {
790
        String[] foreingNameParts = foreingName.split("[.]");
791
        if (foreingNameParts.length != 2) {
792
          continue;
793
        }
794
        String columnNameLocal = foreingNameParts[0];
795
        String columnNameForeing = foreingNameParts[1];
796
        FeatureAttributeDescriptor attr = type.getAttributeDescriptor(columnNameLocal);
797
        if (attr.isForeingKey()) {
798
          ForeingKey foreingKey = attr.getForeingKey();
799
          SQLBuilder.TableNameBuilder foreingTable = sqlbuilder.createTableNameBuilder()
800
                  .database(table.getDatabase())
801
                  .schema(table.getSchema())
802
                  .name(foreingKey.getTableName());
803
          SQLBuilder.TableNameBuilder mainTable = sqlbuilder.createTableNameBuilder()
804
                  .database(table.getDatabase())
805
                  .schema(table.getSchema())
806
                  .name(table.getName());
807

    
808
          sqlbuilder.select().from()
809
                  .left_join(
810
                          foreingTable,
811
                          expbuilder.eq(
812
                                  sqlbuilder.column(mainTable, columnNameLocal),
813
                                  sqlbuilder.column(foreingTable, foreingKey.getCodeName())
814
                          )
815
                  );
816
          sqlbuilder.select().column().name(foreingTable, columnNameForeing);
817
        }
818
      }
819

    
820
    } catch (Throwable th) {
821
      LOGGER.warn("Can't replace FORENG_VALUE function.", th);
822
      throw th;
823
    } finally {
824
      LOGGER.trace("Exit from replaceForeingValueFunction.");
825
    }
826
  }
827
}