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

History | View | Annotate | Download (20 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.CoercionException;
53
import org.gvsig.tools.dataTypes.DataType;
54
import org.gvsig.tools.dispose.DisposableIterator;
55
import org.gvsig.tools.dynobject.DynStruct;
56
import org.gvsig.tools.evaluator.Evaluator;
57
import org.gvsig.tools.evaluator.EvaluatorData;
58
import org.gvsig.tools.evaluator.EvaluatorException;
59
import org.gvsig.tools.evaluator.EvaluatorFieldsInfo;
60
import org.gvsig.tools.observer.Observer;
61
import org.gvsig.tools.persistence.PersistenceManager;
62
import org.gvsig.tools.persistence.PersistentState;
63
import org.gvsig.tools.persistence.exception.PersistenceException;
64
import org.slf4j.Logger;
65
import org.slf4j.LoggerFactory;
66

    
67
public class JoinTransform
68
    extends AbstractFeatureStoreTransform {
69

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

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

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

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

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

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

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

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

    
110
    private JoinTransformEvaluator evaluator = null;
111

    
112
    private FeatureType originalFeatureType;
113

    
114
    private String[] attrsForQuery;
115

    
116
    private String prefix1;
117

    
118
    private String prefix2;
119

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

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

    
152
        if( store1 == store2 ) {
153
            throw new IllegalArgumentException("store1 == store2");
154
        }
155

    
156
        this.setFeatureStore(store1);
157
        this.store2 = store2;
158
        this.keyAttr1 = keyAttr1;
159
        this.keyAttr2 = keyAttr2;
160
        this.prefix1 = prefix1; // TODO
161
        this.prefix2 = prefix2; // TODO
162
        this.attrs = attrs;
163

    
164
    }
165

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

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

    
184
        // keep index of geometry and att desc ==============
185
        int orig_geom_field_index
186
            = this.originalFeatureType.getDefaultGeometryAttributeIndex();
187
        FeatureAttributeDescriptor orig_geom_field_att
188
            = this.originalFeatureType.getDefaultGeometryAttribute();
189

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

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

    
207
            String new_geom_field_name = ed_att.getName();
208
            editableFeatureType.setDefaultGeometryAttributeName(new_geom_field_name);
209
        }
210
        // =====================================================================
211

    
212
        // Add the store 2 fields    
213
        FeatureType featureType2 = store2.getDefaultFeatureType();
214

    
215
        // Add the fields       
216
        for( int i = 0; i < attrs.length; i++ ) {
217
            addFeatureType(editableFeatureType, featureType2.getAttributeDescriptor(attrs[i]), prefix2, store2NamesMap);
218
        }
219

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

    
251
        // assign calculated feature type as this transform's feature type
252
        FeatureType[] types = new FeatureType[]{
253
            editableFeatureType.getNotEditableCopy()};
254
        setFeatureTypes(Arrays.asList(types), types[0]);
255

    
256
        if( store2.getIndexes().getFeatureIndex(keyAttr2)==null ) {
257
            store2.createIndex(featureType2, keyAttr2, keyAttr2, (Observer)null);
258
        }
259
       
260
    }
261

    
262
    private void addFeatureType(EditableFeatureType targetFeatureType, FeatureAttributeDescriptor[] featureAttributeDescriptors,
263
        String prefix, Map<String, String> storeMap) throws DataException {
264

    
265
        for( int i = 0; i < featureAttributeDescriptors.length; i++ ) {
266
            addFeatureType(targetFeatureType, featureAttributeDescriptors[i], prefix, storeMap);
267
        }
268
    }
269

    
270
    private void addFeatureType(EditableFeatureType targetFeatureType, FeatureAttributeDescriptor featureAttributeDescriptor,
271
        String prefix, Map<String, String> storeMap) throws DataException {
272

    
273
        String attName = featureAttributeDescriptor.getName();
274
        if( (prefix != null) && (!prefix.equals("")) ) {
275
            attName = prefix + "_" + attName;
276
        }
277

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

    
284
        EditableFeatureAttributeDescriptor editableFeatureAttributeDescriptor
285
            = targetFeatureType.add(attName, featureAttributeDescriptor.getType(),
286
                featureAttributeDescriptor.getSize());
287
        editableFeatureAttributeDescriptor.setPrecision(featureAttributeDescriptor.getPrecision());
288
        if( featureAttributeDescriptor.getType() == DataTypes.GEOMETRY ) {
289
            editableFeatureAttributeDescriptor.setGeometryType(featureAttributeDescriptor.getGeomType());
290
        }
291
        // keep correspondence between original name and transformed name
292
        storeMap.put(featureAttributeDescriptor.getName(), attName);
293
    }
294

    
295
    /**
296
     *
297
     *
298
     * @param source
299
     *
300
     * @param target
301
     *
302
     * @throws DataException
303
     */
304
    @Override
305
    public void applyTransform(Feature source, EditableFeature target)
306
        throws DataException {
307

    
308
        // copy the data from store1 into the resulting feature
309
        this.copySourceToTarget(source, target);
310

    
311
        // ask store2 for the specified attributes, filtering by the key
312
        // attribute value
313
        // from the source feature
314
        JoinTransformEvaluator eval = this.getEvaluator();
315
        eval.updateValue(source.get(this.keyAttr1));
316

    
317
        FeatureQuery query = store2.createFeatureQuery();
318
        query.setAttributeNames(attrsForQuery);
319
        query.setFilter(eval);
320
        FeatureSet set = null;
321
        DisposableIterator itFeat = null;
322

    
323
        try {
324

    
325
            set = store2.getFeatureSet(query);
326
            // In this join implementation, we will take only the first matching
327
            // feature found in store2
328

    
329
            Feature feat;
330

    
331
            itFeat = set.fastIterator();
332
            if( itFeat.hasNext() ) {
333
                feat = (Feature) itFeat.next();
334

    
335
                // copy all attributes from joined feature to target
336
                this.copyJoinToTarget(feat, target);
337
            }
338
        } finally {
339
            if( itFeat != null ) {
340
                itFeat.dispose();
341
            }
342
            if( set != null ) {
343
                set.dispose();
344
            }
345
        }
346
    }
347

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

    
367
    /**
368
     * @param source
369
     * @param target
370
     */
371
    private void copySourceToTarget(Feature source, EditableFeature target) {
372
        FeatureAttributeDescriptor attr, attrTrg;
373
        FeatureType ftSrc = source.getType();
374
        FeatureType ftTrg = target.getType();
375

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

    
387
                } catch (IllegalArgumentException e) {
388
                    attrTrg = ftTrg.getAttributeDescriptor(attr.getName());
389
                    target.set(attrTrg.getIndex(), attrTrg.getDefaultValue());
390
                }
391

    
392
            }
393
        }
394

    
395
    }
396

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

    
412
    }
413

    
414
    private class JoinTransformEvaluator
415
        implements Evaluator {
416

    
417
        private String attribute;
418
        private boolean isNumeric = false;
419
        private Object value;
420
        private String sql;
421
        private EvaluatorFieldsInfo info = null;
422
        private Coercion coercion;
423

    
424
        //                private int attributeIndex;
425
        public JoinTransformEvaluator(String attribute, boolean is_numeric) {
426
            this.attribute = attribute;
427
            this.isNumeric = is_numeric;
428
            this.value = null;
429
            this.info = new EvaluatorFieldsInfo();
430

    
431
            //                        this.attributeIndex = attrIndex;
432
        }
433

    
434
        public void updateValue(Object value) {
435
            this.value = value;
436
            this.coercion = null;
437
            if (this.value!=null) {
438
                DataType dataType = ToolsLocator.getDataTypesManager().getDataType(value.getClass());
439
                if (dataType!=null) {
440
                    this.coercion = dataType.getCoercion();
441
                }
442
                String qt = this.isNumeric ? "" : "'";
443
                this.sql = this.attribute + " = " + qt + this.value + qt;
444
            } else {
445
                this.sql = this.attribute + " = null";
446
            }
447
            this.info = new EvaluatorFieldsInfo();
448
            this.info.addMatchFieldValue(this.attribute, value);
449
        }
450

    
451
        @Override
452
        public Object evaluate(EvaluatorData arg0) throws EvaluatorException {
453
            Object curValue = arg0.getDataValue(attribute);
454
            if( curValue == null ) {
455
                return value == null;
456
            }
457
            if ( value == null) {
458
                return false;
459
            }
460
            Object coerceValue;
461
            if (this.coercion == null) {
462
                return StringUtils.equals(Objects.toString(curValue), Objects.toString(value));
463
            }
464
            try {
465
                coerceValue = this.coercion.coerce(curValue);
466
                return coerceValue.equals(value);
467
            } catch (CoercionException ex) {
468
                return StringUtils.equals(Objects.toString(curValue), Objects.toString(value));
469
            }
470
        }
471

    
472
        @Override
473
        public String getSQL() {
474
            return this.sql;
475
        }
476

    
477
        @Override
478
        public String getDescription() {
479
            return "Evaluates join transform match";
480
        }
481

    
482
        @Override
483
        public String getName() {
484
            return "JoinTransformEvaluator";
485
        }
486

    
487
        @Override
488
        public EvaluatorFieldsInfo getFieldsInfo() {
489
            return this.info;
490
        }
491

    
492
    }
493

    
494
    @SuppressWarnings("unchecked")
495
    @Override
496
    public FeatureType getSourceFeatureTypeFrom(FeatureType arg0) {
497
        return originalFeatureType;
498
    }
499

    
500
    public static void registerPersistent() {
501
        PersistenceManager persistenceManager = ToolsLocator.getPersistenceManager();
502

    
503
        if( persistenceManager.getDefinition(AbstractFeatureStoreTransform.class) == null ) {
504
            AbstractFeatureStoreTransform.registerPersistent();
505
        }
506

    
507
        DynStruct definition = persistenceManager.getDefinition(PERSISTENCE_DEFINITION_NAME);
508

    
509
        if( definition == null ) {
510
            definition = persistenceManager.addDefinition(
511
                JoinTransform.class,
512
                PERSISTENCE_DEFINITION_NAME,
513
                "JoinTransform Persistence definition",
514
                null,
515
                null
516
            );
517
            definition.extend(PersistenceManager.PERSISTENCE_NAMESPACE,
518
                ABSTRACT_FEATURESTORE_DYNCLASS_NAME);
519

    
520
            definition.addDynFieldObject("store2").setClassOfValue(FeatureStore.class).setMandatory(true);
521
            definition.addDynFieldString("keyAttr1").setMandatory(true);
522
            definition.addDynFieldString("keyAttr2").setMandatory(true);
523
            definition.addDynFieldString("prefix1").setMandatory(false);
524
            definition.addDynFieldString("prefix2").setMandatory(false);
525
            definition.addDynFieldList("attrs").setClassOfItems(String.class).setMandatory(true);
526
        }
527
    }
528

    
529
    @Override
530
    public void saveToState(PersistentState state) throws PersistenceException {
531
        super.saveToState(state);
532
        state.set("store2", this.store2);
533
        state.set("keyAttr1", this.keyAttr1);
534
        state.set("keyAttr2", this.keyAttr2);
535
        state.set("prefix1", this.prefix1);
536
        state.set("prefix2", this.prefix2);
537
        state.set("attrs", this.attrs);
538
    }
539

    
540
    @Override
541
    public void loadFromState(PersistentState state) throws PersistenceException {
542
        super.loadFromState(state);
543
        store2 = (FeatureStore) state.get("store2");
544
        keyAttr1 = state.getString("keyAttr1");
545
        keyAttr2 = state.getString("keyAttr2");
546
        prefix1 = state.getString("prefix1");
547
        prefix2 = state.getString("prefix2");
548
        attrs = (String[]) state.getArray("attrs", String.class);
549
    }
550

    
551
}