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

History | View | Annotate | Download (20.4 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.DataTypes;
37

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

    
68
public class JoinTransform
69
    extends AbstractFeatureStoreTransform {
70

    
71
    private static final Logger logger = LoggerFactory.getLogger(JoinTransform.class);
72

    
73
    public static final String PERSISTENCE_DEFINITION_NAME = "JoinTransform";
74

    
75
    /**
76
     * Store from which the join transform will get the additional attributes
77
     */
78
    private FeatureStore store2;
79

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

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

    
92
    /**
93
     * names of the attributes to join from store2 to store1
94
     */
95
    private String[] attrs;
96

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

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

    
111
    private JoinTransformEvaluator evaluator = null;
112

    
113
    private FeatureType originalFeatureType;
114

    
115
    private String[] attrsForQuery;
116

    
117
    private String prefix1;
118

    
119
    private String prefix2;
120
    
121
    private CoercionContext attr1Context;
122

    
123
    /**
124
     * A default constructor
125
     */
126
    public JoinTransform() {
127
        store1NamesMap = new HashMap<>();
128
        store2NamesMap = new HashMap<>();
129
    }
130

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

    
155
        if( store1 == store2 ) {
156
            throw new IllegalArgumentException("store1 == store2");
157
        }
158

    
159
        this.setFeatureStore(store1);
160
        this.store2 = store2;
161
        this.keyAttr1 = keyAttr1;
162
        this.keyAttr2 = keyAttr2;
163
        this.prefix1 = prefix1; // TODO
164
        this.prefix2 = prefix2; // TODO
165
        this.attrs = attrs;
166
        this.attr1Context = store1.getDefaultFeatureTypeQuietly().getAttributeDescriptor(this.keyAttr1).getCoercionContext();
167

    
168
    }
169

    
170
    private boolean containsIgnoreCase(List<String>l, String item) {
171
        for (String x : l) {
172
            if( StringUtils.equalsIgnoreCase(item, x) ) {
173
                return true;
174
            }
175
        }
176
        return false;
177
    }
178
    
179
    @Override
180
    public void setUp() throws Exception {
181

    
182
        // calculate this transform resulting feature type
183
        // by adding all specified attrs from store2 to store1's default
184
        // feature type
185
        // FIXME for more than one FTypes ??
186
        this.originalFeatureType = this.getFeatureStore().getDefaultFeatureType();
187

    
188
        // keep index of geometry and att desc ==============
189
        int orig_geom_field_index
190
            = this.originalFeatureType.getDefaultGeometryAttributeIndex();
191
        FeatureAttributeDescriptor orig_geom_field_att
192
            = this.originalFeatureType.getDefaultGeometryAttribute();
193

    
194
        // Create the feature type and copy the store 1 type        
195
        EditableFeatureType editableFeatureType = this.getFeatureStore().getDefaultFeatureType().getEditable();
196
        FeatureAttributeDescriptor[] featureAttributeDescriptors = editableFeatureType.getAttributeDescriptors();
197
        for( int i = 0; i < featureAttributeDescriptors.length; i++ ) {
198
            editableFeatureType.remove(featureAttributeDescriptors[i].getName());
199
        }
200
        addFeatureType(editableFeatureType, featureAttributeDescriptors, prefix1, store1NamesMap);
201

    
202
        // =========== set the new geom field name and restore geometry values
203
        if( orig_geom_field_index >= 0 ) {
204
            EditableFeatureAttributeDescriptor ed_att = null;
205
            ed_att = (EditableFeatureAttributeDescriptor) editableFeatureType.getAttributeDescriptor(orig_geom_field_index);
206
            ed_att.setSRS(orig_geom_field_att.getSRS());
207
            ed_att.setObjectClass(orig_geom_field_att.getObjectClass());
208
            ed_att.setGeometryType(orig_geom_field_att.getGeomType());
209
            ed_att.setDefaultValue(orig_geom_field_att.getDefaultValue());
210

    
211
            String new_geom_field_name = ed_att.getName();
212
            editableFeatureType.setDefaultGeometryAttributeName(new_geom_field_name);
213
        }
214
        // =====================================================================
215

    
216
        // Add the store 2 fields    
217
        FeatureType featureType2 = store2.getDefaultFeatureType();
218

    
219
        // Add the fields       
220
        for( int i = 0; i < attrs.length; i++ ) {
221
            addFeatureType(editableFeatureType, featureType2.getAttributeDescriptor(attrs[i]), prefix2, store2NamesMap);
222
        }
223

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

    
255
        // assign calculated feature type as this transform's feature type
256
        FeatureType[] types = new FeatureType[]{
257
            editableFeatureType.getNotEditableCopy()};
258
        setFeatureTypes(Arrays.asList(types), types[0]);
259

    
260
        if( store2.getIndexes().getFeatureIndex(keyAttr2)==null ) {
261
            store2.createIndex(featureType2, keyAttr2, keyAttr2, (Observer)null);
262
        }
263
       
264
    }
265

    
266
    private void addFeatureType(EditableFeatureType targetFeatureType, FeatureAttributeDescriptor[] featureAttributeDescriptors,
267
        String prefix, Map<String, String> storeMap) throws DataException {
268

    
269
        for( int i = 0; i < featureAttributeDescriptors.length; i++ ) {
270
            addFeatureType(targetFeatureType, featureAttributeDescriptors[i], prefix, storeMap);
271
        }
272
    }
273

    
274
    private void addFeatureType(EditableFeatureType targetFeatureType, FeatureAttributeDescriptor featureAttributeDescriptor,
275
        String prefix, Map<String, String> storeMap) throws DataException {
276

    
277
        String attName = featureAttributeDescriptor.getName();
278
        if( (prefix != null) && (!prefix.equals("")) ) {
279
            attName = prefix + "_" + attName;
280
        }
281

    
282
        // If an attribute already exists, calculate an alternate name and add it to our type
283
        int j = 0;
284
        while( targetFeatureType.getIndex(attName) >= 0 ) {
285
            attName = targetFeatureType.getAttributeDescriptor(attName).getName() + "_" + ++j;
286
        }
287

    
288
        EditableFeatureAttributeDescriptor editableFeatureAttributeDescriptor
289
            = targetFeatureType.add(attName, featureAttributeDescriptor.getType(),
290
                featureAttributeDescriptor.getSize());
291
        editableFeatureAttributeDescriptor.setPrecision(featureAttributeDescriptor.getPrecision());
292
        editableFeatureAttributeDescriptor.setScale(featureAttributeDescriptor.getScale());
293
        if( featureAttributeDescriptor.getType() == DataTypes.GEOMETRY ) {
294
            editableFeatureAttributeDescriptor.setGeometryType(featureAttributeDescriptor.getGeomType());
295
        }
296
        // keep correspondence between original name and transformed name
297
        storeMap.put(featureAttributeDescriptor.getName(), attName);
298
    }
299

    
300
    /**
301
     *
302
     *
303
     * @param source
304
     *
305
     * @param target
306
     *
307
     * @throws DataException
308
     */
309
    @Override
310
    public void applyTransform(Feature source, EditableFeature target)
311
        throws DataException {
312

    
313
        // copy the data from store1 into the resulting feature
314
        this.copySourceToTarget(source, target);
315

    
316
        // ask store2 for the specified attributes, filtering by the key
317
        // attribute value
318
        // from the source feature
319
        JoinTransformEvaluator eval = this.getEvaluator();
320
        eval.updateValue(source.get(this.keyAttr1), this.attr1Context);
321

    
322
        FeatureQuery query = store2.createFeatureQuery();
323
        query.setAttributeNames(attrsForQuery);
324
        query.setFilter(eval);
325
        FeatureSet set = null;
326
        DisposableIterator itFeat = null;
327

    
328
        try {
329

    
330
            set = store2.getFeatureSet(query);
331
            // In this join implementation, we will take only the first matching
332
            // feature found in store2
333

    
334
            Feature feat;
335

    
336
            itFeat = set.fastIterator();
337
            if( itFeat.hasNext() ) {
338
                feat = (Feature) itFeat.next();
339

    
340
                // copy all attributes from joined feature to target
341
                this.copyJoinToTarget(feat, target);
342
            }
343
        } finally {
344
            if( itFeat != null ) {
345
                itFeat.dispose();
346
            }
347
            if( set != null ) {
348
                set.dispose();
349
            }
350
        }
351
    }
352

    
353
    /**
354
     * @param feat
355
     * @param target
356
     */
357
    private void copyJoinToTarget(Feature join, EditableFeature target) {
358
        Iterator<Entry<String, String>> iter = store2NamesMap.entrySet()
359
            .iterator();
360
        Entry<String, String> entry;
361
        FeatureType trgType = target.getType();
362
        FeatureAttributeDescriptor attr;
363
        while( iter.hasNext() ) {
364
            entry = iter.next();
365
            attr = trgType.getAttributeDescriptor((String) entry.getValue());
366
            if( attr != null && !attr.isReadOnly() ) {
367
                target.set(attr.getIndex(), join.get((String) entry.getKey()));
368
            }
369
        }
370
    }
371

    
372
    /**
373
     * @param source
374
     * @param target
375
     */
376
    private void copySourceToTarget(Feature source, EditableFeature target) {
377
        FeatureAttributeDescriptor attr, attrTrg;
378
        FeatureType ftSrc = source.getType();
379
        FeatureType ftTrg = target.getType();
380

    
381
        for( int i = 0; i < source.getType().size(); i++ ) {
382
            attr = ftSrc.getAttributeDescriptor(i);
383
            attrTrg = ftTrg.getAttributeDescriptor(store1NamesMap.get(attr.getName()));
384
            if( attrTrg != null ) {
385
                try {
386
                    if( !attrTrg.isReadOnly() ) {
387
                        target.set(attrTrg.getIndex(), source.get(i));
388
                    }
389
                } catch (SetReadOnlyAttributeException e1) {
390
                    // Ignore, do nothing
391

    
392
                } catch (IllegalArgumentException e) {
393
                    attrTrg = ftTrg.getAttributeDescriptor(attr.getName());
394
                    target.set(attrTrg.getIndex(), attrTrg.getDefaultValue());
395
                }
396

    
397
            }
398
        }
399

    
400
    }
401

    
402
    private JoinTransformEvaluator getEvaluator() {
403
        if( this.evaluator == null ) {
404
            FeatureType ft2 = null;
405
            try {
406
                ft2 = this.store2.getDefaultFeatureType();
407
            } catch (DataException e) {
408
                logger.warn("Can't access to the feature type to build the evaluator.", e);
409
                throw new RuntimeException("Can't access to the feature type to build the evaluator.", e);
410
            }
411
            FeatureAttributeDescriptor att2 = ft2.getAttributeDescriptor(keyAttr2);
412
            boolean is_num = att2.getDataType().isNumeric();
413
            this.evaluator = new JoinTransformEvaluator(keyAttr2, is_num);
414
        }
415
        return evaluator;
416

    
417
    }
418

    
419
    private class JoinTransformEvaluator
420
        implements Evaluator {
421

    
422
        private String attribute;
423
        private boolean isNumeric = false;
424
        private Object value;
425
        private String sql;
426
        private EvaluatorFieldsInfo info = null;
427
        private Coercion coercion;
428
        private CoercionContext context;
429

    
430
        //                private int attributeIndex;
431
        public JoinTransformEvaluator(String attribute, boolean is_numeric) {
432
            this.attribute = attribute;
433
            this.isNumeric = is_numeric;
434
            this.value = null;
435
            this.info = new EvaluatorFieldsInfo();
436

    
437
            //                        this.attributeIndex = attrIndex;
438
        }
439

    
440
        public void updateValue(Object value, CoercionContext context) {
441
            this.value = value;
442
            this.coercion = null;
443
            this.context = context;
444
            
445
            if (this.value!=null) {
446
                DataType dataType = ToolsLocator.getDataTypesManager().getDataType(value.getClass());
447
                if (dataType!=null) {
448
                    this.coercion = dataType.getCoercion();
449
                }
450
                String qt = this.isNumeric ? "" : "'";
451
                this.sql = this.attribute + " = " + qt + this.value + qt;
452
            } else {
453
                this.sql = this.attribute + " = null";
454
            }
455
            this.info = new EvaluatorFieldsInfo();
456
            this.info.addMatchFieldValue(this.attribute, value);
457
        }
458

    
459
        @Override
460
        public Object evaluate(EvaluatorData arg0) throws EvaluatorException {
461
            Object curValue = arg0.getDataValue(attribute);
462
            if( curValue == null ) {
463
                return value == null;
464
            }
465
            if ( value == null) {
466
                return false;
467
            }
468
            Object coerceValue;
469
            if (this.coercion == null) {
470
                return StringUtils.equals(Objects.toString(curValue), Objects.toString(value));
471
            }
472
            try {
473
                coerceValue = this.coercion.coerce(curValue, this.context);
474
                return coerceValue.equals(value);
475
            } catch (CoercionException ex) {
476
                return StringUtils.equals(Objects.toString(curValue), Objects.toString(value));
477
            }
478
        }
479

    
480
        @Override
481
        public String getSQL() {
482
            return this.sql;
483
        }
484

    
485
        @Override
486
        public String getDescription() {
487
            return "Evaluates join transform match";
488
        }
489

    
490
        @Override
491
        public String getName() {
492
            return "JoinTransformEvaluator";
493
        }
494

    
495
        @Override
496
        public EvaluatorFieldsInfo getFieldsInfo() {
497
            return this.info;
498
        }
499

    
500
    }
501

    
502
    @SuppressWarnings("unchecked")
503
    @Override
504
    public FeatureType getSourceFeatureTypeFrom(FeatureType arg0) {
505
        return originalFeatureType;
506
    }
507

    
508
    public static void registerPersistent() {
509
        PersistenceManager persistenceManager = ToolsLocator.getPersistenceManager();
510

    
511
        if( persistenceManager.getDefinition(AbstractFeatureStoreTransform.class) == null ) {
512
            AbstractFeatureStoreTransform.registerPersistent();
513
        }
514

    
515
        DynStruct definition = persistenceManager.getDefinition(PERSISTENCE_DEFINITION_NAME);
516

    
517
        if( definition == null ) {
518
            definition = persistenceManager.addDefinition(
519
                JoinTransform.class,
520
                PERSISTENCE_DEFINITION_NAME,
521
                "JoinTransform Persistence definition",
522
                null,
523
                null
524
            );
525
            definition.extend(PersistenceManager.PERSISTENCE_NAMESPACE,
526
                ABSTRACT_FEATURESTORE_DYNCLASS_NAME);
527

    
528
            definition.addDynFieldObject("store2").setClassOfValue(FeatureStore.class).setMandatory(true);
529
            definition.addDynFieldString("keyAttr1").setMandatory(true);
530
            definition.addDynFieldString("keyAttr2").setMandatory(true);
531
            definition.addDynFieldString("prefix1").setMandatory(false);
532
            definition.addDynFieldString("prefix2").setMandatory(false);
533
            definition.addDynFieldList("attrs").setClassOfItems(String.class).setMandatory(true);
534
        }
535
    }
536

    
537
    @Override
538
    public void saveToState(PersistentState state) throws PersistenceException {
539
        super.saveToState(state);
540
        state.set("store2", this.store2);
541
        state.set("keyAttr1", this.keyAttr1);
542
        state.set("keyAttr2", this.keyAttr2);
543
        state.set("prefix1", this.prefix1);
544
        state.set("prefix2", this.prefix2);
545
        state.set("attrs", this.attrs);
546
    }
547

    
548
    @Override
549
    public void loadFromState(PersistentState state) throws PersistenceException {
550
        super.loadFromState(state);
551
        store2 = (FeatureStore) state.get("store2");
552
        keyAttr1 = state.getString("keyAttr1");
553
        keyAttr2 = state.getString("keyAttr2");
554
        prefix1 = state.getString("prefix1");
555
        prefix2 = state.getString("prefix2");
556
        attrs = (String[]) state.getArray("attrs", String.class);
557
    }
558

    
559
}