Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.plugin / org.gvsig.daltransform.app / org.gvsig.daltransform.app.join / src / main / java / org / gvsig / app / join / dal / feature / JoinTransform.java @ 47557

History | View | Annotate | Download (22.5 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 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.app.join.dal.feature;
25

    
26
import java.util.ArrayList;
27
import java.util.Arrays;
28
import java.util.HashMap;
29
import java.util.Iterator;
30
import java.util.List;
31
import java.util.Map;
32
import java.util.Map.Entry;
33
import java.util.Objects;
34
import java.util.logging.Level;
35
import org.apache.commons.lang3.StringUtils;
36
import org.gvsig.fmap.dal.DataStore;
37
import org.gvsig.fmap.dal.DataTypes;
38

    
39
import org.gvsig.fmap.dal.exception.DataException;
40
import org.gvsig.fmap.dal.feature.AbstractFeatureStoreTransform;
41
import org.gvsig.fmap.dal.feature.EditableFeature;
42
import org.gvsig.fmap.dal.feature.EditableFeatureAttributeDescriptor;
43
import org.gvsig.fmap.dal.feature.EditableFeatureType;
44
import org.gvsig.fmap.dal.feature.Feature;
45
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
46
import org.gvsig.fmap.dal.feature.FeatureQuery;
47
import org.gvsig.fmap.dal.feature.FeatureSet;
48
import org.gvsig.fmap.dal.feature.FeatureStore;
49
import org.gvsig.fmap.dal.feature.FeatureType;
50
import org.gvsig.fmap.dal.feature.exception.SetReadOnlyAttributeException;
51
import org.gvsig.tools.ToolsLocator;
52
import org.gvsig.tools.dataTypes.Coercion;
53
import org.gvsig.tools.dataTypes.CoercionContext;
54
import org.gvsig.tools.dataTypes.CoercionException;
55
import org.gvsig.tools.dataTypes.DataType;
56
import org.gvsig.tools.dispose.DisposableIterator;
57
import org.gvsig.tools.dynobject.DynObject;
58
import org.gvsig.tools.dynobject.DynStruct;
59
import org.gvsig.tools.dynobject.exception.DynFieldNotFoundException;
60
import org.gvsig.tools.evaluator.Evaluator;
61
import org.gvsig.tools.evaluator.EvaluatorData;
62
import org.gvsig.tools.evaluator.EvaluatorException;
63
import org.gvsig.tools.evaluator.EvaluatorFieldsInfo;
64
import org.gvsig.tools.observer.Observer;
65
import org.gvsig.tools.persistence.PersistenceManager;
66
import org.gvsig.tools.persistence.PersistentState;
67
import org.gvsig.tools.persistence.exception.PersistenceException;
68
import org.slf4j.Logger;
69
import org.slf4j.LoggerFactory;
70

    
71
public class JoinTransform
72
    extends AbstractFeatureStoreTransform {
73

    
74
    private static final Logger logger = LoggerFactory.getLogger(JoinTransform.class);
75

    
76
    public static final String PERSISTENCE_DEFINITION_NAME = "JoinTransform";
77

    
78
    /**
79
     * Store from which the join transform will get the additional attributes
80
     */
81
    private FeatureStore store2;
82

    
83
    /**
84
     * name of the key attr in store1 that will be used to match features in
85
     * store2
86
     */
87
    private String keyAttr1;
88

    
89
    /**
90
     * name of the key attr in store2 that will be used to match features in
91
     * store1
92
     */
93
    private String keyAttr2;
94

    
95
    /**
96
     * names of the attributes to join from store2 to store1
97
     */
98
    private String[] attrs;
99

    
100
    /**
101
     * Attribute names may change after transformation a prefix is applied. This
102
     * map keeps correspondence between store1 original names and their
103
     * transformed counterparts.
104
     */
105
    private final Map<String, String> store1NamesMap;
106

    
107
    /**
108
     * Attribute names may change after transformation if they are repeated in
109
     * both stores or if a prefix is applied. This map keeps correspondence
110
     * between store2 original names and their transformed counterparts.
111
     */
112
    private final Map<String, String> store2NamesMap;
113

    
114
    private JoinTransformEvaluator evaluator = null;
115

    
116
    private FeatureType originalFeatureType;
117

    
118
    private String[] attrsForQuery;
119

    
120
    private String prefix1;
121

    
122
    private String prefix2;
123
    
124
    private CoercionContext attr1Context;
125
    private boolean hasGeometryAdded;
126
    private Object previousMetadataEnvelope;
127

    
128
    /**
129
     * A default constructor
130
     */
131
    public JoinTransform() {
132
        store1NamesMap = new HashMap<>();
133
        store2NamesMap = new HashMap<>();
134
        this.hasGeometryAdded = false;
135
    }
136

    
137
    /**
138
     * Initializes all the necessary data for this transform
139
     *
140
     * @param store1 store whose default feature type is the target of this
141
     * transform
142
     *
143
     * @param store2 store whose default feature type will provide the new
144
     * attributes to join
145
     *
146
     * @param keyAttr1 key attribute in store1 that matches keyAttr2 in store2
147
     * (foreign key), used for joining both stores.
148
     *
149
     * @param keyAttr2 key attribute in store2 that matches keyAttr1 in store2
150
     * (foreign key), used for joining both stores.
151
     * @param prefix1
152
     * @param prefix2
153
     *
154
     * @param attrs names of the attributes in store2 that will be joined to
155
     * store1.
156
     */
157
    public void setValues(FeatureStore store1, FeatureStore store2,
158
        String keyAttr1, String keyAttr2, String prefix1, String prefix2,
159
        String[] attrs) {
160

    
161
        if( store1 == store2 ) {
162
            throw new IllegalArgumentException("store1 == store2");
163
        }
164

    
165
        this.setFeatureStore(store1);
166
        this.store2 = store2;
167
        this.keyAttr1 = keyAttr1;
168
        this.keyAttr2 = keyAttr2;
169
        this.prefix1 = prefix1; // TODO
170
        this.prefix2 = prefix2; // TODO
171
        this.attrs = attrs;
172
        this.attr1Context = store1.getDefaultFeatureTypeQuietly().getAttributeDescriptor(this.keyAttr1).getCoercionContext();
173

    
174
    }
175

    
176
    private boolean containsIgnoreCase(List<String>l, String item) {
177
        for (String x : l) {
178
            if( StringUtils.equalsIgnoreCase(item, x) ) {
179
                return true;
180
            }
181
        }
182
        return false;
183
    }
184
    
185
    @Override
186
    public void setUp() throws Exception {
187

    
188
        // calculate this transform resulting feature type
189
        // by adding all specified attrs from store2 to store1's default
190
        // feature type
191
        // FIXME for more than one FTypes ??
192
        this.originalFeatureType = this.getFeatureStore().getDefaultFeatureType();
193

    
194
        // keep index of geometry and att desc ==============
195
        int orig_geom_field_index
196
            = this.originalFeatureType.getDefaultGeometryAttributeIndex();
197
        FeatureAttributeDescriptor orig_geom_field_att
198
            = this.originalFeatureType.getDefaultGeometryAttribute();
199

    
200
        // Create the feature type and copy the store 1 type        
201
        EditableFeatureType editableFeatureType = this.getFeatureStore().getDefaultFeatureType().getEditable();
202
        FeatureAttributeDescriptor[] featureAttributeDescriptors = editableFeatureType.getAttributeDescriptors();
203
        for( int i = 0; i < featureAttributeDescriptors.length; i++ ) {
204
            editableFeatureType.remove(featureAttributeDescriptors[i].getName());
205
        }
206
        addFeatureType(editableFeatureType, featureAttributeDescriptors, prefix1, store1NamesMap);
207

    
208
        // =========== set the new geom field name and restore geometry values
209
        if( orig_geom_field_index >= 0 ) {
210
            EditableFeatureAttributeDescriptor ed_att = null;
211
            ed_att = (EditableFeatureAttributeDescriptor) editableFeatureType.getAttributeDescriptor(orig_geom_field_index);
212
            ed_att.setSRS(orig_geom_field_att.getSRS());
213
            ed_att.setObjectClass(orig_geom_field_att.getObjectClass());
214
            ed_att.setGeometryType(orig_geom_field_att.getGeomType());
215
            ed_att.setDefaultValue(orig_geom_field_att.getDefaultValue());
216

    
217
            String new_geom_field_name = ed_att.getName();
218
            editableFeatureType.setDefaultGeometryAttributeName(new_geom_field_name);
219
        }
220
        // =====================================================================
221

    
222
        // Add the store 2 fields    
223
        FeatureType featureType2 = store2.getDefaultFeatureType();
224

    
225
        // Add the fields       
226
        for( int i = 0; i < attrs.length; i++ ) {
227
            addFeatureType(editableFeatureType, featureType2.getAttributeDescriptor(attrs[i]), prefix2, store2NamesMap);
228
        }
229

    
230
        List<String> list1 = new ArrayList<>();
231
        for (String attr : this.attrs) {
232
            if( !containsIgnoreCase(list1, attr) ) {
233
                list1.add(attr);
234
            }
235
        }
236
        if( !containsIgnoreCase(list1, keyAttr2 ) ) {
237
            list1.add(keyAttr2);
238
        }
239
        List<String> list2 = new ArrayList<>(list1);
240
        for (String attrname : list1) {
241
            FeatureAttributeDescriptor attrdesc = featureType2.getAttributeDescriptor(attrname);
242
            String[] requiredAttrs = attrdesc.getRequiredFieldNames();
243
            if( requiredAttrs!=null ) {
244
                for (String requiredAttr : requiredAttrs) {
245
                    if( !containsIgnoreCase(list2, requiredAttr) ) {
246
                        list2.add(requiredAttr);
247
                    }
248
                }
249
            }
250
        }
251
        this.attrsForQuery = (String[]) list2.toArray(new String[]{});
252
//        if( this.store2NamesMap.containsKey(keyAttr2) ) {
253
//            this.attrsForQuery = this.attrs;
254
//        } else {
255
//            List<String> list = new ArrayList<String>(this.attrs.length + 1);
256
//            list.addAll(Arrays.asList(this.attrs));
257
//            list.add(keyAttr2);
258
//            this.attrsForQuery = (String[]) list.toArray(new String[]{});
259
//        }
260

    
261
        // assign calculated feature type as this transform's feature type
262
        FeatureType[] types = new FeatureType[]{
263
            editableFeatureType.getNotEditableCopy()};
264
        setFeatureTypes(Arrays.asList(types), types[0]);
265

    
266
        if( store2.getIndexes().getFeatureIndex(keyAttr2)==null ) {
267
            store2.createIndex(featureType2, keyAttr2, keyAttr2, (Observer)null);
268
        }
269
       
270
    }
271

    
272
    private void addFeatureType(EditableFeatureType targetFeatureType, FeatureAttributeDescriptor[] featureAttributeDescriptors,
273
        String prefix, Map<String, String> storeMap) throws DataException {
274

    
275
        for( int i = 0; i < featureAttributeDescriptors.length; i++ ) {
276
            addFeatureType(targetFeatureType, featureAttributeDescriptors[i], prefix, storeMap);
277
        }
278
    }
279

    
280
    private void addFeatureType(EditableFeatureType targetFeatureType, FeatureAttributeDescriptor featureAttributeDescriptor,
281
        String prefix, Map<String, String> storeMap) throws DataException {
282

    
283
        String attName = featureAttributeDescriptor.getName();
284
        if( (prefix != null) && (!prefix.equals("")) ) {
285
            attName = prefix + attName;
286
        }
287
        
288
        FeatureAttributeDescriptor geomAttr = targetFeatureType.getDefaultGeometryAttribute();
289

    
290
        // If an attribute already exists, calculate an alternate name and add it to our type
291
        int j = 0;
292
        while( targetFeatureType.getIndex(attName) >= 0 ) {
293
            attName = targetFeatureType.getAttributeDescriptor(attName).getName() + (++j);
294
        }
295

    
296
        EditableFeatureAttributeDescriptor editableFeatureAttributeDescriptor
297
            = targetFeatureType.add(attName, featureAttributeDescriptor.getType(),
298
                featureAttributeDescriptor.getSize());
299
        editableFeatureAttributeDescriptor.setPrecision(featureAttributeDescriptor.getPrecision());
300
        editableFeatureAttributeDescriptor.setScale(featureAttributeDescriptor.getScale());
301
        if( featureAttributeDescriptor.getType() == DataTypes.GEOMETRY ) {
302
            editableFeatureAttributeDescriptor.setGeometryType(featureAttributeDescriptor.getGeomType());
303
            if(geomAttr == null) {
304
                this.hasGeometryAdded = true;
305
            }
306
        }
307
        // keep correspondence between original name and transformed name
308
        storeMap.put(featureAttributeDescriptor.getName(), attName);
309
    }
310

    
311
    /**
312
     *
313
     *
314
     * @param source
315
     *
316
     * @param target
317
     *
318
     * @throws DataException
319
     */
320
    @Override
321
    public void applyTransform(Feature source, EditableFeature target)
322
        throws DataException {
323

    
324
        // copy the data from store1 into the resulting feature
325
        this.copySourceToTarget(source, target);
326

    
327
        // ask store2 for the specified attributes, filtering by the key
328
        // attribute value
329
        // from the source feature
330
        JoinTransformEvaluator eval = this.getEvaluator();
331
        eval.updateValue(source.get(this.keyAttr1), this.attr1Context);
332

    
333
        FeatureQuery query = store2.createFeatureQuery();
334
        query.setAttributeNames(attrsForQuery);
335
        query.setFilter(eval);
336
        FeatureSet set = null;
337
        DisposableIterator itFeat = null;
338

    
339
        try {
340

    
341
            set = store2.getFeatureSet(query);
342
            // In this join implementation, we will take only the first matching
343
            // feature found in store2
344

    
345
            Feature feat;
346

    
347
            itFeat = set.fastIterator();
348
            if( itFeat.hasNext() ) {
349
                feat = (Feature) itFeat.next();
350

    
351
                // copy all attributes from joined feature to target
352
                this.copyJoinToTarget(feat, target);
353
            }
354
        } finally {
355
            if( itFeat != null ) {
356
                itFeat.dispose();
357
            }
358
            if( set != null ) {
359
                set.dispose();
360
            }
361
        }
362
    }
363

    
364
    /**
365
     * @param feat
366
     * @param target
367
     */
368
    private void copyJoinToTarget(Feature join, EditableFeature target) {
369
        Iterator<Entry<String, String>> iter = store2NamesMap.entrySet()
370
            .iterator();
371
        Entry<String, String> entry;
372
        FeatureType trgType = target.getType();
373
        FeatureAttributeDescriptor attr;
374
        while( iter.hasNext() ) {
375
            entry = iter.next();
376
            attr = trgType.getAttributeDescriptor((String) entry.getValue());
377
            if( attr != null && !attr.isReadOnly() ) {
378
                target.set(attr.getIndex(), join.get((String) entry.getKey()));
379
            }
380
        }
381
    }
382

    
383
    /**
384
     * @param source
385
     * @param target
386
     */
387
    private void copySourceToTarget(Feature source, EditableFeature target) {
388
        FeatureAttributeDescriptor attr, attrTrg;
389
        FeatureType ftSrc = source.getType();
390
        FeatureType ftTrg = target.getType();
391

    
392
        for( int i = 0; i < source.getType().size(); i++ ) {
393
            attr = ftSrc.getAttributeDescriptor(i);
394
            attrTrg = ftTrg.getAttributeDescriptor(store1NamesMap.get(attr.getName()));
395
            if( attrTrg != null ) {
396
                try {
397
                    if( !attrTrg.isReadOnly() ) {
398
                        target.set(attrTrg.getIndex(), source.get(i));
399
                    }
400
                } catch (SetReadOnlyAttributeException e1) {
401
                    // Ignore, do nothing
402

    
403
                } catch (IllegalArgumentException e) {
404
                    attrTrg = ftTrg.getAttributeDescriptor(attr.getName());
405
                    target.set(attrTrg.getIndex(), attrTrg.getDefaultValue());
406
                }
407

    
408
            }
409
        }
410

    
411
    }
412

    
413
    private JoinTransformEvaluator getEvaluator() {
414
        if( this.evaluator == null ) {
415
            FeatureType ft2 = null;
416
            try {
417
                ft2 = this.store2.getDefaultFeatureType();
418
            } catch (DataException e) {
419
                logger.warn("Can't access to the feature type to build the evaluator.", e);
420
                throw new RuntimeException("Can't access to the feature type to build the evaluator.", e);
421
            }
422
            FeatureAttributeDescriptor att2 = ft2.getAttributeDescriptor(keyAttr2);
423
            boolean is_num = att2.getDataType().isNumeric();
424
            this.evaluator = new JoinTransformEvaluator(keyAttr2, is_num);
425
        }
426
        return evaluator;
427

    
428
    }
429

    
430
    private class JoinTransformEvaluator
431
        implements Evaluator {
432

    
433
        private String attribute;
434
        private boolean isNumeric = false;
435
        private Object value;
436
        private String sql;
437
        private EvaluatorFieldsInfo info = null;
438
        private Coercion coercion;
439
        private CoercionContext context;
440

    
441
        //                private int attributeIndex;
442
        public JoinTransformEvaluator(String attribute, boolean is_numeric) {
443
            this.attribute = attribute;
444
            this.isNumeric = is_numeric;
445
            this.value = null;
446
            this.info = new EvaluatorFieldsInfo();
447

    
448
            //                        this.attributeIndex = attrIndex;
449
        }
450

    
451
        public void updateValue(Object value, CoercionContext context) {
452
            this.value = value;
453
            this.coercion = null;
454
            this.context = context;
455
            
456
            if (this.value!=null) {
457
                DataType dataType = ToolsLocator.getDataTypesManager().getDataType(value.getClass());
458
                if (dataType!=null) {
459
                    this.coercion = dataType.getCoercion();
460
                }
461
                String qt = this.isNumeric ? "" : "'";
462
                this.sql = this.attribute + " = " + qt + this.value + qt;
463
            } else {
464
                this.sql = this.attribute + " = null";
465
            }
466
            this.info = new EvaluatorFieldsInfo();
467
            this.info.addMatchFieldValue(this.attribute, value);
468
        }
469

    
470
        @Override
471
        public Object evaluate(EvaluatorData arg0) throws EvaluatorException {
472
            Object curValue = arg0.getDataValue(attribute);
473
            if( curValue == null ) {
474
                return value == null;
475
            }
476
            if ( value == null) {
477
                return false;
478
            }
479
            Object coerceValue;
480
            if (this.coercion == null) {
481
                return StringUtils.equals(Objects.toString(curValue), Objects.toString(value));
482
            }
483
            try {
484
                coerceValue = this.coercion.coerce(curValue, this.context);
485
                return coerceValue.equals(value);
486
            } catch (CoercionException ex) {
487
                return StringUtils.equals(Objects.toString(curValue), Objects.toString(value));
488
            }
489
        }
490

    
491
        @Override
492
        public String getSQL() {
493
            return this.sql;
494
        }
495

    
496
        @Override
497
        public String getDescription() {
498
            return "Evaluates join transform match";
499
        }
500

    
501
        @Override
502
        public String getName() {
503
            return "JoinTransformEvaluator";
504
        }
505

    
506
        @Override
507
        public EvaluatorFieldsInfo getFieldsInfo() {
508
            return this.info;
509
        }
510

    
511
    }
512

    
513
    @SuppressWarnings("unchecked")
514
    @Override
515
    public FeatureType getSourceFeatureTypeFrom(FeatureType arg0) {
516
        return originalFeatureType;
517
    }
518

    
519
    public static void registerPersistent() {
520
        PersistenceManager persistenceManager = ToolsLocator.getPersistenceManager();
521

    
522
        if( persistenceManager.getDefinition(AbstractFeatureStoreTransform.class) == null ) {
523
            AbstractFeatureStoreTransform.registerPersistent();
524
        }
525

    
526
        DynStruct definition = persistenceManager.getDefinition(PERSISTENCE_DEFINITION_NAME);
527

    
528
        if( definition == null ) {
529
            definition = persistenceManager.addDefinition(
530
                JoinTransform.class,
531
                PERSISTENCE_DEFINITION_NAME,
532
                "JoinTransform Persistence definition",
533
                null,
534
                null
535
            );
536
            definition.extend(PersistenceManager.PERSISTENCE_NAMESPACE,
537
                ABSTRACT_FEATURESTORE_DYNCLASS_NAME);
538

    
539
            definition.addDynFieldObject("store2").setClassOfValue(FeatureStore.class).setMandatory(true);
540
            definition.addDynFieldString("keyAttr1").setMandatory(true);
541
            definition.addDynFieldString("keyAttr2").setMandatory(true);
542
            definition.addDynFieldString("prefix1").setMandatory(false);
543
            definition.addDynFieldString("prefix2").setMandatory(false);
544
            definition.addDynFieldList("attrs").setClassOfItems(String.class).setMandatory(true);
545
        }
546
    }
547

    
548
    @Override
549
    public void saveToState(PersistentState state) throws PersistenceException {
550
        super.saveToState(state);
551
        state.set("store2", this.store2);
552
        state.set("keyAttr1", this.keyAttr1);
553
        state.set("keyAttr2", this.keyAttr2);
554
        state.set("prefix1", this.prefix1);
555
        state.set("prefix2", this.prefix2);
556
        state.set("attrs", this.attrs);
557
    }
558

    
559
    @Override
560
    public void loadFromState(PersistentState state) throws PersistenceException {
561
        super.loadFromState(state);
562
        store2 = (FeatureStore) state.get("store2");
563
        keyAttr1 = state.getString("keyAttr1");
564
        keyAttr2 = state.getString("keyAttr2");
565
        prefix1 = state.getString("prefix1");
566
        prefix2 = state.getString("prefix2");
567
        attrs = (String[]) state.getArray("attrs", String.class);
568
    }
569

    
570
//    @Override
571
//    public void apply(FeatureStore store) {
572
//        super.apply(store);
573
//        if(hasGeometryAdded){
574
//            try {
575
//                this.previousMetadataEnvelope = this.getSourceMetadata().getDynValue(DataStore.METADATA_ENVELOPE);
576
//                this.getSourceMetadata().setDynValue(DataStore.METADATA_ENVELOPE, this.store2.getEnvelope());
577
//            } catch (DataException ex) {
578
//                logger.warn("Can't calculate envelope of transform '"+DataStore.getNameQuietly(this.getFeatureStore())+"'", ex);
579
//            }
580
//        }
581
//    }
582
//
583
//    @Override
584
//    public void revoke(FeatureStore store) {
585
//        super.revoke(store);
586
//        this.getSourceMetadata().setDynValue(DataStore.METADATA_ENVELOPE, this.previousMetadataEnvelope);
587
//    }
588
    
589
    @Override
590
    public Object getDynValue(String name) throws DynFieldNotFoundException {
591
        if(super.hasDynValue(name)) {
592
            return super.getDynValue(name);
593
        }
594
        switch(name){
595
            case DataStore.METADATA_ENVELOPE:
596
            case DataStore.METADATA_CRS:
597
                if(hasGeometryAdded){
598
                    return this.store2.getDynValue(name);
599
                }
600
                break;
601
        }
602
        return null;
603
    }
604

    
605
    @Override
606
    public boolean hasDynValue(String name) {
607
        if(super.hasDynValue(name)) {
608
            return true;
609
        }
610
        switch(name){
611
            case DataStore.METADATA_ENVELOPE:
612
            case DataStore.METADATA_CRS:
613
                if(hasGeometryAdded){
614
                    return this.store2.hasDynValue(name);
615
                }
616
            default:
617
                return false;
618
        }
619
    }
620

    
621
}