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

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

    
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 extends AbstractFeatureStoreTransform {
60

    
61
        private static Logger logger = LoggerFactory.getLogger(JoinTransform.class);
62
        
63
    public static final String PERSISTENCE_DEFINITION_NAME = "JoinTransform";
64
    
65
    /**
66
     * Store from which the join transform will get the additional attributes
67
     */
68
    private FeatureStore store2;
69

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

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

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

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

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

    
101
    private JoinTransformEvaluator evaluator = null;
102

    
103
    private FeatureType originalFeatureType;
104

    
105
    private String[] attrsForQuery;
106

    
107
    private String prefix1;
108

    
109
    private String prefix2;
110

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

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

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

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

    
160
        // calculate this transform resulting feature type
161
        // by adding all specified attrs from store2 to store1's default
162
        // feature type
163
        // FIXME for more than one FTypes ??
164
        this.originalFeatureType = this.getFeatureStore()
165
        .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)
185
                editableFeatureType.getAttributeDescriptor(orig_geom_field_index);
186
            ed_att.setSRS(orig_geom_field_att.getSRS());
187
            ed_att.setObjectClass(orig_geom_field_att.getObjectClass());
188
            ed_att.setGeometryType(orig_geom_field_att.getGeomType());
189
            ed_att.setDefaultValue(orig_geom_field_att.getDefaultValue());
190

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

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

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

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

    
213
        // assign calculated feature type as this transform's feature type
214
        FeatureType[] types = new FeatureType[] { 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
    public void applyTransform(Feature source, EditableFeature target)
259
    throws DataException {
260

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

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

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

    
276
        try {
277

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

    
282
            Feature feat;
283

    
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

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

    
345
            }
346
        }
347

    
348
    }
349

    
350
    private JoinTransformEvaluator getEvaluator() {
351
        if (this.evaluator == null){
352
                FeatureType ft2 = null;
353
                try {
354
                                ft2 = this.store2.getDefaultFeatureType();
355
                        } catch (DataException e) {
356
                                logger.error("While getting feat type.", 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 implements Evaluator {
367

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

    
374
        //                private int attributeIndex;
375

    
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
        public Object evaluate(EvaluatorData arg0) throws EvaluatorException {
394
            Object curValue = arg0.getDataValue(attribute);
395
            if (curValue == null) {
396
                return value == null;
397
            }
398
            return curValue.equals(value);
399
        }
400

    
401
        public String getSQL() {
402
            return this.sql;
403
        }
404

    
405
        public String getDescription() {
406
            return "Evaluates join transform match";
407
        }
408

    
409
        public String getName() {
410
            return "JoinTransformEvaluator";
411
        }
412

    
413
        public EvaluatorFieldsInfo getFieldsInfo() {
414
            return this.info;
415
        }
416

    
417
    }        
418

    
419
    @SuppressWarnings("unchecked")
420
    public FeatureType getSourceFeatureTypeFrom(FeatureType arg0) {
421
        return originalFeatureType;
422
    }
423

    
424
    public boolean isTransformsOriginalValues() {
425
        return false;
426
    }
427

    
428
    public static void registerPersistent() {
429
        PersistenceManager persistenceManager = ToolsLocator.getPersistenceManager();
430

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

    
435
        DynStruct definition = persistenceManager.getDefinition(PERSISTENCE_DEFINITION_NAME);
436

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

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

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

    
467
    public void loadFromState(PersistentState state) throws PersistenceException {
468
        super.loadFromState(state);
469
        FeatureStore store2 = (FeatureStore) state.get("store2");
470
        String keyAttr1 = state.getString("keyAttr1");
471
        String keyAttr2 = state.getString("keyAttr2");
472
        String prefix1 =  state.getString("prefix1");
473
        String prefix2 =  state.getString("prefix2");
474
        String[] attrs = (String[]) state.getArray("attrs", String.class);
475
        try {
476
            initialize(getFeatureStore(), store2, keyAttr1, keyAttr2, prefix1, prefix2, attrs);
477
        } catch (DataException e) {
478
            throw new PersistenceException("Impossible to create the transform", e);
479
        }
480
    }
481

    
482
}