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

History | View | Annotate | Download (17.2 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

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

    
59
public class JoinTransform
60
    extends AbstractFeatureStoreTransform {
61

    
62
    private static final Logger logger = LoggerFactory.getLogger(JoinTransform.class);
63

    
64
    public static final String PERSISTENCE_DEFINITION_NAME = "JoinTransform";
65

    
66
    /**
67
     * Store from which the join transform will get the additional attributes
68
     */
69
    private FeatureStore store2;
70

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

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

    
83
    /**
84
     * names of the attributes to join from store2 to store1
85
     */
86
    private String[] attrs;
87

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

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

    
102
    private JoinTransformEvaluator evaluator = null;
103

    
104
    private FeatureType originalFeatureType;
105

    
106
    private String[] attrsForQuery;
107

    
108
    private String prefix1;
109

    
110
    private String prefix2;
111

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

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

    
144
        if( store1 == store2 ) {
145
            throw new IllegalArgumentException("store1 == store2");
146
        }
147

    
148
        this.setFeatureStore(store1);
149
        this.store2 = store2;
150
        this.keyAttr1 = keyAttr1;
151
        this.keyAttr2 = keyAttr2;
152
        this.prefix1 = prefix1; // TODO
153
        this.prefix2 = prefix2; // TODO
154
        this.attrs = attrs;
155

    
156
    }
157

    
158
    @Override
159
    public void setUp() throws Exception {
160

    
161
        // calculate this transform resulting feature type
162
        // by adding all specified attrs from store2 to store1's default
163
        // feature type
164
        // FIXME for more than one FTypes ??
165
        this.originalFeatureType = this.getFeatureStore().getDefaultFeatureType();
166

    
167
        // keep index of geometry and att desc ==============
168
        int orig_geom_field_index
169
            = this.originalFeatureType.getDefaultGeometryAttributeIndex();
170
        FeatureAttributeDescriptor orig_geom_field_att
171
            = this.originalFeatureType.getDefaultGeometryAttribute();
172

    
173
        // Create the feature type and copy the store 1 type        
174
        EditableFeatureType editableFeatureType = this.getFeatureStore().getDefaultFeatureType().getEditable();
175
        FeatureAttributeDescriptor[] featureAttributeDescriptors = editableFeatureType.getAttributeDescriptors();
176
        for( int i = 0; i < featureAttributeDescriptors.length; i++ ) {
177
            editableFeatureType.remove(featureAttributeDescriptors[i].getName());
178
        }
179
        addFeatureType(editableFeatureType, featureAttributeDescriptors, prefix1, store1NamesMap);
180

    
181
        // =========== set the new geom field name and restore geometry values
182
        if( orig_geom_field_index >= 0 ) {
183
            EditableFeatureAttributeDescriptor ed_att = null;
184
            ed_att = (EditableFeatureAttributeDescriptor) editableFeatureType.getAttributeDescriptor(orig_geom_field_index);
185
            ed_att.setSRS(orig_geom_field_att.getSRS());
186
            ed_att.setObjectClass(orig_geom_field_att.getObjectClass());
187
            ed_att.setGeometryType(orig_geom_field_att.getGeomType());
188
            ed_att.setDefaultValue(orig_geom_field_att.getDefaultValue());
189

    
190
            String new_geom_field_name = ed_att.getName();
191
            editableFeatureType.setDefaultGeometryAttributeName(new_geom_field_name);
192
        }
193
        // =====================================================================
194

    
195
        // Add the store 2 fields    
196
        FeatureType featureType2 = store2.getDefaultFeatureType();
197

    
198
        // Add the fields       
199
        for( int i = 0; i < attrs.length; i++ ) {
200
            addFeatureType(editableFeatureType, featureType2.getAttributeDescriptor(attrs[i]), prefix2, store2NamesMap);
201
        }
202

    
203
        if( this.store2NamesMap.containsKey(keyAttr2) ) {
204
            this.attrsForQuery = this.attrs;
205
        } else {
206
            List<String> list = new ArrayList<String>(this.attrs.length + 1);
207
            list.addAll(Arrays.asList(this.attrs));
208
            list.add(keyAttr2);
209
            this.attrsForQuery = (String[]) list.toArray(new String[]{});
210
        }
211

    
212
        // assign calculated feature type as this transform's feature type
213
        FeatureType[] types = new FeatureType[]{
214
            editableFeatureType.getNotEditableCopy()};
215
        setFeatureTypes(Arrays.asList(types), types[0]);
216
    }
217

    
218
    private void addFeatureType(EditableFeatureType targetFeatureType, FeatureAttributeDescriptor[] featureAttributeDescriptors,
219
        String prefix, Map<String, String> storeMap) throws DataException {
220

    
221
        for( int i = 0; i < featureAttributeDescriptors.length; i++ ) {
222
            addFeatureType(targetFeatureType, featureAttributeDescriptors[i], prefix, storeMap);
223
        }
224
    }
225

    
226
    private void addFeatureType(EditableFeatureType targetFeatureType, FeatureAttributeDescriptor featureAttributeDescriptor,
227
        String prefix, Map<String, String> storeMap) throws DataException {
228

    
229
        String attName = featureAttributeDescriptor.getName();
230
        if( (prefix != null) && (!prefix.equals("")) ) {
231
            attName = prefix + "_" + attName;
232
        }
233

    
234
        // If an attribute already exists, calculate an alternate name and add it to our type
235
        int j = 0;
236
        while( targetFeatureType.getIndex(attName) >= 0 ) {
237
            attName = targetFeatureType.getAttributeDescriptor(attName).getName() + "_" + ++j;
238
        }
239

    
240
        EditableFeatureAttributeDescriptor editableFeatureAttributeDescriptor
241
            = targetFeatureType.add(attName, featureAttributeDescriptor.getType(),
242
                featureAttributeDescriptor.getSize());
243
        editableFeatureAttributeDescriptor.setPrecision(featureAttributeDescriptor.getPrecision());
244

    
245
        // keep correspondence between original name and transformed name
246
        storeMap.put(featureAttributeDescriptor.getName(), attName);
247
    }
248

    
249
    /**
250
     *
251
     *
252
     * @param source
253
     *
254
     * @param target
255
     *
256
     * @throws DataException
257
     */
258
    @Override
259
    public void applyTransform(Feature source, EditableFeature target)
260
        throws DataException {
261

    
262
        // copy the data from store1 into the resulting feature
263
        this.copySourceToTarget(source, target);
264

    
265
        // ask store2 for the specified attributes, filtering by the key
266
        // attribute value
267
        // from the source feature
268
        JoinTransformEvaluator eval = this.getEvaluator();
269
        eval.updateValue(source.get(this.keyAttr1));
270

    
271
        FeatureQuery query = store2.createFeatureQuery();
272
        query.setAttributeNames(attrsForQuery);
273
        query.setFilter(eval);
274
        FeatureSet set = null;
275
        DisposableIterator itFeat = null;
276

    
277
        try {
278

    
279
            set = store2.getFeatureSet(query);
280
            // In this join implementation, we will take only the first matching
281
            // feature found in store2
282

    
283
            Feature feat;
284

    
285
            itFeat = set.fastIterator();
286
            if( itFeat.hasNext() ) {
287
                feat = (Feature) itFeat.next();
288

    
289
                // copy all attributes from joined feature to target
290
                this.copyJoinToTarget(feat, target);
291
            }
292
        } finally {
293
            if( itFeat != null ) {
294
                itFeat.dispose();
295
            }
296
            if( set != null ) {
297
                set.dispose();
298
            }
299
        }
300
    }
301

    
302
    /**
303
     * @param feat
304
     * @param target
305
     */
306
    private void copyJoinToTarget(Feature join, EditableFeature target) {
307
        Iterator<Entry<String, String>> iter = store2NamesMap.entrySet()
308
            .iterator();
309
        Entry<String, String> entry;
310
        FeatureType trgType = target.getType();
311
        FeatureAttributeDescriptor attr;
312
        while( iter.hasNext() ) {
313
            entry = iter.next();
314
            attr = trgType.getAttributeDescriptor((String) entry.getValue());
315
            if( attr != null ) {
316
                target.set(attr.getIndex(), join.get((String) entry.getKey()));
317
            }
318
        }
319
    }
320

    
321
    /**
322
     * @param source
323
     * @param target
324
     */
325
    private void copySourceToTarget(Feature source, EditableFeature target) {
326
        FeatureAttributeDescriptor attr, attrTrg;
327
        FeatureType ftSrc = source.getType();
328
        FeatureType ftTrg = target.getType();
329

    
330
        for( int i = 0; i < source.getType().size(); i++ ) {
331
            attr = ftSrc.getAttributeDescriptor(i);
332
            attrTrg = ftTrg.getAttributeDescriptor(store1NamesMap.get(attr.getName()));
333
            if( attrTrg != null ) {
334
                try {
335
                    target.set(attrTrg.getIndex(), source.get(i));
336
                } catch (SetReadOnlyAttributeException e1) {
337
                    // Ignore, do nothing
338

    
339
                } catch (IllegalArgumentException e) {
340
                    attrTrg = ftTrg.getAttributeDescriptor(attr.getName());
341
                    target.set(attrTrg.getIndex(), attrTrg.getDefaultValue());
342
                }
343

    
344
            }
345
        }
346

    
347
    }
348

    
349
    private JoinTransformEvaluator getEvaluator() {
350
        if( this.evaluator == null ) {
351
            FeatureType ft2 = null;
352
            try {
353
                ft2 = this.store2.getDefaultFeatureType();
354
            } catch (DataException e) {
355
                logger.warn("Can't access to the feature type to build the evaluator.", e);
356
                throw new RuntimeException("Can't access to the feature type to build the evaluator.", e);
357
            }
358
            FeatureAttributeDescriptor att2 = ft2.getAttributeDescriptor(keyAttr2);
359
            boolean is_num = att2.getDataType().isNumeric();
360
            this.evaluator = new JoinTransformEvaluator(keyAttr2, is_num);
361
        }
362
        return evaluator;
363

    
364
    }
365

    
366
    private class JoinTransformEvaluator
367
        implements Evaluator {
368

    
369
        private String attribute;
370
        private boolean isNumeric = false;
371
        private Object value;
372
        private String sql;
373
        private EvaluatorFieldsInfo info = null;
374

    
375
        //                private int attributeIndex;
376
        public JoinTransformEvaluator(String attribute, boolean is_numeric) {
377
            this.attribute = attribute;
378
            this.isNumeric = is_numeric;
379
            this.value = null;
380
            this.info = new EvaluatorFieldsInfo();
381

    
382
            //                        this.attributeIndex = attrIndex;
383
        }
384

    
385
        public void updateValue(Object value) {
386
            this.value = value;
387
            String qt = this.isNumeric ? "" : "'";
388
            this.sql = this.attribute + " = " + qt + this.value + qt;
389
            this.info = new EvaluatorFieldsInfo();
390
            this.info.addMatchFieldValue(this.attribute, value);
391
        }
392

    
393
        @Override
394
        public Object evaluate(EvaluatorData arg0) throws EvaluatorException {
395
            Object curValue = arg0.getDataValue(attribute);
396
            if( curValue == null ) {
397
                return value == null;
398
            }
399
            return curValue.equals(value);
400
        }
401

    
402
        @Override
403
        public String getSQL() {
404
            return this.sql;
405
        }
406

    
407
        @Override
408
        public String getDescription() {
409
            return "Evaluates join transform match";
410
        }
411

    
412
        @Override
413
        public String getName() {
414
            return "JoinTransformEvaluator";
415
        }
416

    
417
        @Override
418
        public EvaluatorFieldsInfo getFieldsInfo() {
419
            return this.info;
420
        }
421

    
422
    }
423

    
424
    @SuppressWarnings("unchecked")
425
    @Override
426
    public FeatureType getSourceFeatureTypeFrom(FeatureType arg0) {
427
        return originalFeatureType;
428
    }
429

    
430
    public static void registerPersistent() {
431
        PersistenceManager persistenceManager = ToolsLocator.getPersistenceManager();
432

    
433
        if( persistenceManager.getDefinition(AbstractFeatureStoreTransform.class) == null ) {
434
            AbstractFeatureStoreTransform.registerPersistent();
435
        }
436

    
437
        DynStruct definition = persistenceManager.getDefinition(PERSISTENCE_DEFINITION_NAME);
438

    
439
        if( definition == null ) {
440
            definition = persistenceManager.addDefinition(
441
                JoinTransform.class,
442
                PERSISTENCE_DEFINITION_NAME,
443
                "JoinTransform Persistence definition",
444
                null,
445
                null
446
            );
447
            definition.extend(PersistenceManager.PERSISTENCE_NAMESPACE,
448
                ABSTRACT_FEATURESTORE_DYNCLASS_NAME);
449

    
450
            definition.addDynFieldObject("store2").setClassOfValue(FeatureStore.class).setMandatory(true);
451
            definition.addDynFieldString("keyAttr1").setMandatory(true);
452
            definition.addDynFieldString("keyAttr2").setMandatory(true);
453
            definition.addDynFieldString("prefix1").setMandatory(false);
454
            definition.addDynFieldString("prefix2").setMandatory(false);
455
            definition.addDynFieldList("attrs").setClassOfItems(String.class).setMandatory(true);
456
        }
457
    }
458

    
459
    @Override
460
    public void saveToState(PersistentState state) throws PersistenceException {
461
        super.saveToState(state);
462
        state.set("store2", this.store2);
463
        state.set("keyAttr1", this.keyAttr1);
464
        state.set("keyAttr2", this.keyAttr2);
465
        state.set("prefix1", this.prefix1);
466
        state.set("prefix2", this.prefix2);
467
        state.set("attrs", this.attrs);
468
    }
469

    
470
    @Override
471
    public void loadFromState(PersistentState state) throws PersistenceException {
472
        super.loadFromState(state);
473
        store2 = (FeatureStore) state.get("store2");
474
        keyAttr1 = state.getString("keyAttr1");
475
        keyAttr2 = state.getString("keyAttr2");
476
        prefix1 = state.getString("prefix1");
477
        prefix2 = state.getString("prefix2");
478
        attrs = (String[]) state.getArray("attrs", String.class);
479
    }
480

    
481
}