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

History | View | Annotate | Download (18.9 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 org.apache.commons.lang3.StringUtils;
34
import org.gvsig.fmap.dal.DataTypes;
35

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

    
62
public class JoinTransform
63
    extends AbstractFeatureStoreTransform {
64

    
65
    private static final Logger logger = LoggerFactory.getLogger(JoinTransform.class);
66

    
67
    public static final String PERSISTENCE_DEFINITION_NAME = "JoinTransform";
68

    
69
    /**
70
     * Store from which the join transform will get the additional attributes
71
     */
72
    private FeatureStore store2;
73

    
74
    /**
75
     * name of the key attr in store1 that will be used to match features in
76
     * store2
77
     */
78
    private String keyAttr1;
79

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

    
86
    /**
87
     * names of the attributes to join from store2 to store1
88
     */
89
    private String[] attrs;
90

    
91
    /**
92
     * Attribute names may change after transformation a prefix is applied. This
93
     * map keeps correspondence between store1 original names and their
94
     * transformed counterparts.
95
     */
96
    private final Map<String, String> store1NamesMap;
97

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

    
105
    private JoinTransformEvaluator evaluator = null;
106

    
107
    private FeatureType originalFeatureType;
108

    
109
    private String[] attrsForQuery;
110

    
111
    private String prefix1;
112

    
113
    private String prefix2;
114

    
115
    /**
116
     * A default constructor
117
     */
118
    public JoinTransform() {
119
        store1NamesMap = new HashMap<>();
120
        store2NamesMap = new HashMap<>();
121
    }
122

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

    
147
        if( store1 == store2 ) {
148
            throw new IllegalArgumentException("store1 == store2");
149
        }
150

    
151
        this.setFeatureStore(store1);
152
        this.store2 = store2;
153
        this.keyAttr1 = keyAttr1;
154
        this.keyAttr2 = keyAttr2;
155
        this.prefix1 = prefix1; // TODO
156
        this.prefix2 = prefix2; // TODO
157
        this.attrs = attrs;
158

    
159
    }
160

    
161
    private boolean containsIgnoreCase(List<String>l, String item) {
162
        for (String x : l) {
163
            if( StringUtils.equalsIgnoreCase(item, x) ) {
164
                return true;
165
            }
166
        }
167
        return false;
168
    }
169
    
170
    @Override
171
    public void setUp() throws Exception {
172

    
173
        // calculate this transform resulting feature type
174
        // by adding all specified attrs from store2 to store1's default
175
        // feature type
176
        // FIXME for more than one FTypes ??
177
        this.originalFeatureType = this.getFeatureStore().getDefaultFeatureType();
178

    
179
        // keep index of geometry and att desc ==============
180
        int orig_geom_field_index
181
            = this.originalFeatureType.getDefaultGeometryAttributeIndex();
182
        FeatureAttributeDescriptor orig_geom_field_att
183
            = this.originalFeatureType.getDefaultGeometryAttribute();
184

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

    
193
        // =========== set the new geom field name and restore geometry values
194
        if( orig_geom_field_index >= 0 ) {
195
            EditableFeatureAttributeDescriptor ed_att = null;
196
            ed_att = (EditableFeatureAttributeDescriptor) editableFeatureType.getAttributeDescriptor(orig_geom_field_index);
197
            ed_att.setSRS(orig_geom_field_att.getSRS());
198
            ed_att.setObjectClass(orig_geom_field_att.getObjectClass());
199
            ed_att.setGeometryType(orig_geom_field_att.getGeomType());
200
            ed_att.setDefaultValue(orig_geom_field_att.getDefaultValue());
201

    
202
            String new_geom_field_name = ed_att.getName();
203
            editableFeatureType.setDefaultGeometryAttributeName(new_geom_field_name);
204
        }
205
        // =====================================================================
206

    
207
        // Add the store 2 fields    
208
        FeatureType featureType2 = store2.getDefaultFeatureType();
209

    
210
        // Add the fields       
211
        for( int i = 0; i < attrs.length; i++ ) {
212
            addFeatureType(editableFeatureType, featureType2.getAttributeDescriptor(attrs[i]), prefix2, store2NamesMap);
213
        }
214

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

    
246
        // assign calculated feature type as this transform's feature type
247
        FeatureType[] types = new FeatureType[]{
248
            editableFeatureType.getNotEditableCopy()};
249
        setFeatureTypes(Arrays.asList(types), types[0]);
250

    
251
        if( store2.getIndexes().getFeatureIndex(keyAttr2)==null ) {
252
            store2.createIndex(featureType2, keyAttr2, keyAttr2, (Observer)null);
253
        }
254
       
255
    }
256

    
257
    private void addFeatureType(EditableFeatureType targetFeatureType, FeatureAttributeDescriptor[] featureAttributeDescriptors,
258
        String prefix, Map<String, String> storeMap) throws DataException {
259

    
260
        for( int i = 0; i < featureAttributeDescriptors.length; i++ ) {
261
            addFeatureType(targetFeatureType, featureAttributeDescriptors[i], prefix, storeMap);
262
        }
263
    }
264

    
265
    private void addFeatureType(EditableFeatureType targetFeatureType, FeatureAttributeDescriptor featureAttributeDescriptor,
266
        String prefix, Map<String, String> storeMap) throws DataException {
267

    
268
        String attName = featureAttributeDescriptor.getName();
269
        if( (prefix != null) && (!prefix.equals("")) ) {
270
            attName = prefix + "_" + attName;
271
        }
272

    
273
        // If an attribute already exists, calculate an alternate name and add it to our type
274
        int j = 0;
275
        while( targetFeatureType.getIndex(attName) >= 0 ) {
276
            attName = targetFeatureType.getAttributeDescriptor(attName).getName() + "_" + ++j;
277
        }
278

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

    
290
    /**
291
     *
292
     *
293
     * @param source
294
     *
295
     * @param target
296
     *
297
     * @throws DataException
298
     */
299
    @Override
300
    public void applyTransform(Feature source, EditableFeature target)
301
        throws DataException {
302

    
303
        // copy the data from store1 into the resulting feature
304
        this.copySourceToTarget(source, target);
305

    
306
        // ask store2 for the specified attributes, filtering by the key
307
        // attribute value
308
        // from the source feature
309
        JoinTransformEvaluator eval = this.getEvaluator();
310
        eval.updateValue(source.get(this.keyAttr1));
311

    
312
        FeatureQuery query = store2.createFeatureQuery();
313
        query.setAttributeNames(attrsForQuery);
314
        query.setFilter(eval);
315
        FeatureSet set = null;
316
        DisposableIterator itFeat = null;
317

    
318
        try {
319

    
320
            set = store2.getFeatureSet(query);
321
            // In this join implementation, we will take only the first matching
322
            // feature found in store2
323

    
324
            Feature feat;
325

    
326
            itFeat = set.fastIterator();
327
            if( itFeat.hasNext() ) {
328
                feat = (Feature) itFeat.next();
329

    
330
                // copy all attributes from joined feature to target
331
                this.copyJoinToTarget(feat, target);
332
            }
333
        } finally {
334
            if( itFeat != null ) {
335
                itFeat.dispose();
336
            }
337
            if( set != null ) {
338
                set.dispose();
339
            }
340
        }
341
    }
342

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

    
362
    /**
363
     * @param source
364
     * @param target
365
     */
366
    private void copySourceToTarget(Feature source, EditableFeature target) {
367
        FeatureAttributeDescriptor attr, attrTrg;
368
        FeatureType ftSrc = source.getType();
369
        FeatureType ftTrg = target.getType();
370

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

    
382
                } catch (IllegalArgumentException e) {
383
                    attrTrg = ftTrg.getAttributeDescriptor(attr.getName());
384
                    target.set(attrTrg.getIndex(), attrTrg.getDefaultValue());
385
                }
386

    
387
            }
388
        }
389

    
390
    }
391

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

    
407
    }
408

    
409
    private class JoinTransformEvaluator
410
        implements Evaluator {
411

    
412
        private String attribute;
413
        private boolean isNumeric = false;
414
        private Object value;
415
        private String sql;
416
        private EvaluatorFieldsInfo info = null;
417

    
418
        //                private int attributeIndex;
419
        public JoinTransformEvaluator(String attribute, boolean is_numeric) {
420
            this.attribute = attribute;
421
            this.isNumeric = is_numeric;
422
            this.value = null;
423
            this.info = new EvaluatorFieldsInfo();
424

    
425
            //                        this.attributeIndex = attrIndex;
426
        }
427

    
428
        public void updateValue(Object value) {
429
            this.value = value;
430
            String qt = this.isNumeric ? "" : "'";
431
            this.sql = this.attribute + " = " + qt + this.value + qt;
432
            this.info = new EvaluatorFieldsInfo();
433
            this.info.addMatchFieldValue(this.attribute, value);
434
        }
435

    
436
        @Override
437
        public Object evaluate(EvaluatorData arg0) throws EvaluatorException {
438
            Object curValue = arg0.getDataValue(attribute);
439
            if( curValue == null ) {
440
                return value == null;
441
            }
442
            return curValue.equals(value);
443
        }
444

    
445
        @Override
446
        public String getSQL() {
447
            return this.sql;
448
        }
449

    
450
        @Override
451
        public String getDescription() {
452
            return "Evaluates join transform match";
453
        }
454

    
455
        @Override
456
        public String getName() {
457
            return "JoinTransformEvaluator";
458
        }
459

    
460
        @Override
461
        public EvaluatorFieldsInfo getFieldsInfo() {
462
            return this.info;
463
        }
464

    
465
    }
466

    
467
    @SuppressWarnings("unchecked")
468
    @Override
469
    public FeatureType getSourceFeatureTypeFrom(FeatureType arg0) {
470
        return originalFeatureType;
471
    }
472

    
473
    public static void registerPersistent() {
474
        PersistenceManager persistenceManager = ToolsLocator.getPersistenceManager();
475

    
476
        if( persistenceManager.getDefinition(AbstractFeatureStoreTransform.class) == null ) {
477
            AbstractFeatureStoreTransform.registerPersistent();
478
        }
479

    
480
        DynStruct definition = persistenceManager.getDefinition(PERSISTENCE_DEFINITION_NAME);
481

    
482
        if( definition == null ) {
483
            definition = persistenceManager.addDefinition(
484
                JoinTransform.class,
485
                PERSISTENCE_DEFINITION_NAME,
486
                "JoinTransform Persistence definition",
487
                null,
488
                null
489
            );
490
            definition.extend(PersistenceManager.PERSISTENCE_NAMESPACE,
491
                ABSTRACT_FEATURESTORE_DYNCLASS_NAME);
492

    
493
            definition.addDynFieldObject("store2").setClassOfValue(FeatureStore.class).setMandatory(true);
494
            definition.addDynFieldString("keyAttr1").setMandatory(true);
495
            definition.addDynFieldString("keyAttr2").setMandatory(true);
496
            definition.addDynFieldString("prefix1").setMandatory(false);
497
            definition.addDynFieldString("prefix2").setMandatory(false);
498
            definition.addDynFieldList("attrs").setClassOfItems(String.class).setMandatory(true);
499
        }
500
    }
501

    
502
    @Override
503
    public void saveToState(PersistentState state) throws PersistenceException {
504
        super.saveToState(state);
505
        state.set("store2", this.store2);
506
        state.set("keyAttr1", this.keyAttr1);
507
        state.set("keyAttr2", this.keyAttr2);
508
        state.set("prefix1", this.prefix1);
509
        state.set("prefix2", this.prefix2);
510
        state.set("attrs", this.attrs);
511
    }
512

    
513
    @Override
514
    public void loadFromState(PersistentState state) throws PersistenceException {
515
        super.loadFromState(state);
516
        store2 = (FeatureStore) state.get("store2");
517
        keyAttr1 = state.getString("keyAttr1");
518
        keyAttr2 = state.getString("keyAttr2");
519
        prefix1 = state.getString("prefix1");
520
        prefix2 = state.getString("prefix2");
521
        attrs = (String[]) state.getArray("attrs", String.class);
522
    }
523

    
524
}