Statistics
| Revision:

svn-gvsig-desktop / branches / org.gvsig.desktop-2018a / org.gvsig.desktop.plugin / org.gvsig.daltransform.app / org.gvsig.daltransform.app.join / src / main / java / org / gvsig / app / join / dal / feature / JoinTransform.java @ 43888

History | View | Annotate | Download (17.5 KB)

1 40558 jjdelcerro
/**
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 40435 jjdelcerro
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 43888 jjdelcerro
import org.gvsig.fmap.dal.DataTypes;
34 40435 jjdelcerro
35
import org.gvsig.fmap.dal.exception.DataException;
36
import org.gvsig.fmap.dal.feature.AbstractFeatureStoreTransform;
37
import org.gvsig.fmap.dal.feature.EditableFeature;
38
import org.gvsig.fmap.dal.feature.EditableFeatureAttributeDescriptor;
39
import org.gvsig.fmap.dal.feature.EditableFeatureType;
40
import org.gvsig.fmap.dal.feature.Feature;
41
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
42
import org.gvsig.fmap.dal.feature.FeatureQuery;
43
import org.gvsig.fmap.dal.feature.FeatureSet;
44
import org.gvsig.fmap.dal.feature.FeatureStore;
45
import org.gvsig.fmap.dal.feature.FeatureType;
46 41252 jjdelcerro
import org.gvsig.fmap.dal.feature.exception.SetReadOnlyAttributeException;
47 40435 jjdelcerro
import org.gvsig.tools.ToolsLocator;
48
import org.gvsig.tools.dispose.DisposableIterator;
49
import org.gvsig.tools.dynobject.DynStruct;
50
import org.gvsig.tools.evaluator.Evaluator;
51
import org.gvsig.tools.evaluator.EvaluatorData;
52
import org.gvsig.tools.evaluator.EvaluatorException;
53
import org.gvsig.tools.evaluator.EvaluatorFieldsInfo;
54
import org.gvsig.tools.persistence.PersistenceManager;
55
import org.gvsig.tools.persistence.PersistentState;
56
import org.gvsig.tools.persistence.exception.PersistenceException;
57 40733 jldominguez
import org.slf4j.Logger;
58
import org.slf4j.LoggerFactory;
59 40435 jjdelcerro
60 43215 jjdelcerro
public class JoinTransform
61
    extends AbstractFeatureStoreTransform {
62 40435 jjdelcerro
63 43215 jjdelcerro
    private static final Logger logger = LoggerFactory.getLogger(JoinTransform.class);
64
65 40435 jjdelcerro
    public static final String PERSISTENCE_DEFINITION_NAME = "JoinTransform";
66 43215 jjdelcerro
67 40435 jjdelcerro
    /**
68
     * Store from which the join transform will get the additional attributes
69
     */
70
    private FeatureStore store2;
71
72
    /**
73
     * name of the key attr in store1 that will be used to match features in
74
     * store2
75
     */
76
    private String keyAttr1;
77
78
    /**
79
     * name of the key attr in store2 that will be used to match features in
80
     * store1
81
     */
82
    private String keyAttr2;
83
84
    /**
85
     * names of the attributes to join from store2 to store1
86
     */
87
    private String[] attrs;
88
89
    /**
90 43215 jjdelcerro
     * Attribute names may change after transformation a prefix is applied. This
91
     * map keeps correspondence between store1 original names and their
92
     * transformed counterparts.
93 40435 jjdelcerro
     */
94 43215 jjdelcerro
    private final Map<String, String> store1NamesMap;
95 40435 jjdelcerro
96
    /**
97
     * Attribute names may change after transformation if they are repeated in
98
     * both stores or if a prefix is applied. This map keeps correspondence
99
     * between store2 original names and their transformed counterparts.
100
     */
101 43215 jjdelcerro
    private final Map<String, String> store2NamesMap;
102 40435 jjdelcerro
103
    private JoinTransformEvaluator evaluator = null;
104
105
    private FeatureType originalFeatureType;
106
107
    private String[] attrsForQuery;
108
109
    private String prefix1;
110
111
    private String prefix2;
112
113
    /**
114
     * A default constructor
115
     */
116
    public JoinTransform() {
117 43215 jjdelcerro
        store1NamesMap = new HashMap<>();
118
        store2NamesMap = new HashMap<>();
119 40435 jjdelcerro
    }
120
121
    /**
122
     * Initializes all the necessary data for this transform
123
     *
124 43215 jjdelcerro
     * @param store1 store whose default feature type is the target of this
125
     * transform
126 40435 jjdelcerro
     *
127 43215 jjdelcerro
     * @param store2 store whose default feature type will provide the new
128
     * attributes to join
129 40435 jjdelcerro
     *
130 43215 jjdelcerro
     * @param keyAttr1 key attribute in store1 that matches keyAttr2 in store2
131
     * (foreign key), used for joining both stores.
132 40435 jjdelcerro
     *
133 43215 jjdelcerro
     * @param keyAttr2 key attribute in store2 that matches keyAttr1 in store2
134
     * (foreign key), used for joining both stores.
135
     * @param prefix1
136
     * @param prefix2
137 40435 jjdelcerro
     *
138 43215 jjdelcerro
     * @param attrs names of the attributes in store2 that will be joined to
139
     * store1.
140 40435 jjdelcerro
     */
141 43215 jjdelcerro
    public void setValues(FeatureStore store1, FeatureStore store2,
142 40435 jjdelcerro
        String keyAttr1, String keyAttr2, String prefix1, String prefix2,
143 43215 jjdelcerro
        String[] attrs) {
144 40435 jjdelcerro
145 43215 jjdelcerro
        if( store1 == store2 ) {
146 40435 jjdelcerro
            throw new IllegalArgumentException("store1 == store2");
147
        }
148
149
        this.setFeatureStore(store1);
150
        this.store2 = store2;
151
        this.keyAttr1 = keyAttr1;
152
        this.keyAttr2 = keyAttr2;
153
        this.prefix1 = prefix1; // TODO
154
        this.prefix2 = prefix2; // TODO
155
        this.attrs = attrs;
156
157 43215 jjdelcerro
    }
158
159
    @Override
160
    public void setUp() throws Exception {
161
162 40435 jjdelcerro
        // calculate this transform resulting feature type
163
        // by adding all specified attrs from store2 to store1's default
164
        // feature type
165
        // FIXME for more than one FTypes ??
166 43215 jjdelcerro
        this.originalFeatureType = this.getFeatureStore().getDefaultFeatureType();
167 40435 jjdelcerro
168
        // keep index of geometry and att desc ==============
169 43215 jjdelcerro
        int orig_geom_field_index
170
            = this.originalFeatureType.getDefaultGeometryAttributeIndex();
171
        FeatureAttributeDescriptor orig_geom_field_att
172
            = this.originalFeatureType.getDefaultGeometryAttribute();
173
174 40435 jjdelcerro
        // Create the feature type and copy the store 1 type
175
        EditableFeatureType editableFeatureType = this.getFeatureStore().getDefaultFeatureType().getEditable();
176
        FeatureAttributeDescriptor[] featureAttributeDescriptors = editableFeatureType.getAttributeDescriptors();
177 43215 jjdelcerro
        for( int i = 0; i < featureAttributeDescriptors.length; i++ ) {
178
            editableFeatureType.remove(featureAttributeDescriptors[i].getName());
179
        }
180 40435 jjdelcerro
        addFeatureType(editableFeatureType, featureAttributeDescriptors, prefix1, store1NamesMap);
181
182
        // =========== set the new geom field name and restore geometry values
183 43215 jjdelcerro
        if( orig_geom_field_index >= 0 ) {
184 40435 jjdelcerro
            EditableFeatureAttributeDescriptor ed_att = null;
185 43215 jjdelcerro
            ed_att = (EditableFeatureAttributeDescriptor) editableFeatureType.getAttributeDescriptor(orig_geom_field_index);
186 40435 jjdelcerro
            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 43215 jjdelcerro
        for( int i = 0; i < attrs.length; i++ ) {
201
            addFeatureType(editableFeatureType, featureType2.getAttributeDescriptor(attrs[i]), prefix2, store2NamesMap);
202 40435 jjdelcerro
        }
203
204 43215 jjdelcerro
        if( this.store2NamesMap.containsKey(keyAttr2) ) {
205 40435 jjdelcerro
            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 43215 jjdelcerro
            this.attrsForQuery = (String[]) list.toArray(new String[]{});
211 40435 jjdelcerro
        }
212
213
        // assign calculated feature type as this transform's feature type
214 43215 jjdelcerro
        FeatureType[] types = new FeatureType[]{
215
            editableFeatureType.getNotEditableCopy()};
216 40435 jjdelcerro
        setFeatureTypes(Arrays.asList(types), types[0]);
217
    }
218
219
    private void addFeatureType(EditableFeatureType targetFeatureType, FeatureAttributeDescriptor[] featureAttributeDescriptors,
220 43215 jjdelcerro
        String prefix, Map<String, String> storeMap) throws DataException {
221 40435 jjdelcerro
222 43215 jjdelcerro
        for( int i = 0; i < featureAttributeDescriptors.length; i++ ) {
223 40435 jjdelcerro
            addFeatureType(targetFeatureType, featureAttributeDescriptors[i], prefix, storeMap);
224
        }
225
    }
226
227
    private void addFeatureType(EditableFeatureType targetFeatureType, FeatureAttributeDescriptor featureAttributeDescriptor,
228 43215 jjdelcerro
        String prefix, Map<String, String> storeMap) throws DataException {
229 40435 jjdelcerro
230
        String attName = featureAttributeDescriptor.getName();
231 43215 jjdelcerro
        if( (prefix != null) && (!prefix.equals("")) ) {
232 40435 jjdelcerro
            attName = prefix + "_" + attName;
233
        }
234
235
        // If an attribute already exists, calculate an alternate name and add it to our type
236
        int j = 0;
237 43215 jjdelcerro
        while( targetFeatureType.getIndex(attName) >= 0 ) {
238 40435 jjdelcerro
            attName = targetFeatureType.getAttributeDescriptor(attName).getName() + "_" + ++j;
239
        }
240
241 43215 jjdelcerro
        EditableFeatureAttributeDescriptor editableFeatureAttributeDescriptor
242
            = targetFeatureType.add(attName, featureAttributeDescriptor.getType(),
243 40435 jjdelcerro
                featureAttributeDescriptor.getSize());
244
        editableFeatureAttributeDescriptor.setPrecision(featureAttributeDescriptor.getPrecision());
245 43888 jjdelcerro
        if( featureAttributeDescriptor.getType() == DataTypes.GEOMETRY ) {
246
            editableFeatureAttributeDescriptor.setGeometryType(featureAttributeDescriptor.getGeomType());
247
        }
248 40435 jjdelcerro
        // keep correspondence between original name and transformed name
249
        storeMap.put(featureAttributeDescriptor.getName(), attName);
250 43215 jjdelcerro
    }
251 40435 jjdelcerro
252
    /**
253
     *
254
     *
255
     * @param source
256
     *
257
     * @param target
258
     *
259
     * @throws DataException
260
     */
261 43215 jjdelcerro
    @Override
262 40435 jjdelcerro
    public void applyTransform(Feature source, EditableFeature target)
263 43215 jjdelcerro
        throws DataException {
264 40435 jjdelcerro
265
        // copy the data from store1 into the resulting feature
266
        this.copySourceToTarget(source, target);
267
268
        // ask store2 for the specified attributes, filtering by the key
269
        // attribute value
270
        // from the source feature
271
        JoinTransformEvaluator eval = this.getEvaluator();
272
        eval.updateValue(source.get(this.keyAttr1));
273
274
        FeatureQuery query = store2.createFeatureQuery();
275
        query.setAttributeNames(attrsForQuery);
276
        query.setFilter(eval);
277
        FeatureSet set = null;
278
        DisposableIterator itFeat = null;
279
280
        try {
281
282
            set = store2.getFeatureSet(query);
283
            // In this join implementation, we will take only the first matching
284
            // feature found in store2
285
286
            Feature feat;
287
288 42488 jjdelcerro
            itFeat = set.fastIterator();
289 43215 jjdelcerro
            if( itFeat.hasNext() ) {
290 40435 jjdelcerro
                feat = (Feature) itFeat.next();
291
292
                // copy all attributes from joined feature to target
293
                this.copyJoinToTarget(feat, target);
294
            }
295
        } finally {
296 43215 jjdelcerro
            if( itFeat != null ) {
297 40435 jjdelcerro
                itFeat.dispose();
298
            }
299 43215 jjdelcerro
            if( set != null ) {
300 40435 jjdelcerro
                set.dispose();
301
            }
302
        }
303
    }
304
305
    /**
306
     * @param feat
307
     * @param target
308
     */
309
    private void copyJoinToTarget(Feature join, EditableFeature target) {
310
        Iterator<Entry<String, String>> iter = store2NamesMap.entrySet()
311 43215 jjdelcerro
            .iterator();
312 40435 jjdelcerro
        Entry<String, String> entry;
313
        FeatureType trgType = target.getType();
314
        FeatureAttributeDescriptor attr;
315 43215 jjdelcerro
        while( iter.hasNext() ) {
316 40435 jjdelcerro
            entry = iter.next();
317
            attr = trgType.getAttributeDescriptor((String) entry.getValue());
318 43422 jjdelcerro
            if( attr != null && !attr.isReadOnly() ) {
319 40435 jjdelcerro
                target.set(attr.getIndex(), join.get((String) entry.getKey()));
320
            }
321
        }
322
    }
323
324
    /**
325
     * @param source
326
     * @param target
327
     */
328
    private void copySourceToTarget(Feature source, EditableFeature target) {
329
        FeatureAttributeDescriptor attr, attrTrg;
330
        FeatureType ftSrc = source.getType();
331
        FeatureType ftTrg = target.getType();
332
333 43215 jjdelcerro
        for( int i = 0; i < source.getType().size(); i++ ) {
334 40435 jjdelcerro
            attr = ftSrc.getAttributeDescriptor(i);
335
            attrTrg = ftTrg.getAttributeDescriptor(store1NamesMap.get(attr.getName()));
336 43215 jjdelcerro
            if( attrTrg != null ) {
337 40435 jjdelcerro
                try {
338 43422 jjdelcerro
                    if( !attrTrg.isReadOnly() ) {
339
                        target.set(attrTrg.getIndex(), source.get(i));
340
                    }
341 43215 jjdelcerro
                } catch (SetReadOnlyAttributeException e1) {
342 41252 jjdelcerro
                    // Ignore, do nothing
343 43215 jjdelcerro
344 40435 jjdelcerro
                } catch (IllegalArgumentException e) {
345
                    attrTrg = ftTrg.getAttributeDescriptor(attr.getName());
346
                    target.set(attrTrg.getIndex(), attrTrg.getDefaultValue());
347
                }
348
349
            }
350
        }
351
352
    }
353
354
    private JoinTransformEvaluator getEvaluator() {
355 43215 jjdelcerro
        if( this.evaluator == null ) {
356
            FeatureType ft2 = null;
357
            try {
358
                ft2 = this.store2.getDefaultFeatureType();
359
            } catch (DataException e) {
360
                logger.warn("Can't access to the feature type to build the evaluator.", e);
361
                throw new RuntimeException("Can't access to the feature type to build the evaluator.", e);
362
            }
363
            FeatureAttributeDescriptor att2 = ft2.getAttributeDescriptor(keyAttr2);
364
            boolean is_num = att2.getDataType().isNumeric();
365 40733 jldominguez
            this.evaluator = new JoinTransformEvaluator(keyAttr2, is_num);
366 40435 jjdelcerro
        }
367
        return evaluator;
368
369
    }
370
371 43215 jjdelcerro
    private class JoinTransformEvaluator
372
        implements Evaluator {
373 40435 jjdelcerro
374
        private String attribute;
375 40733 jldominguez
        private boolean isNumeric = false;
376 40435 jjdelcerro
        private Object value;
377
        private String sql;
378
        private EvaluatorFieldsInfo info = null;
379
380
        //                private int attributeIndex;
381 40733 jldominguez
        public JoinTransformEvaluator(String attribute, boolean is_numeric) {
382 40435 jjdelcerro
            this.attribute = attribute;
383 40733 jldominguez
            this.isNumeric = is_numeric;
384 40435 jjdelcerro
            this.value = null;
385
            this.info = new EvaluatorFieldsInfo();
386
387
            //                        this.attributeIndex = attrIndex;
388
        }
389
390
        public void updateValue(Object value) {
391
            this.value = value;
392 40733 jldominguez
            String qt = this.isNumeric ? "" : "'";
393
            this.sql = this.attribute + " = " + qt + this.value + qt;
394 40435 jjdelcerro
            this.info = new EvaluatorFieldsInfo();
395
            this.info.addMatchFieldValue(this.attribute, value);
396
        }
397
398 43215 jjdelcerro
        @Override
399 40435 jjdelcerro
        public Object evaluate(EvaluatorData arg0) throws EvaluatorException {
400
            Object curValue = arg0.getDataValue(attribute);
401 43215 jjdelcerro
            if( curValue == null ) {
402 40435 jjdelcerro
                return value == null;
403
            }
404
            return curValue.equals(value);
405
        }
406
407 43215 jjdelcerro
        @Override
408 40435 jjdelcerro
        public String getSQL() {
409
            return this.sql;
410
        }
411
412 43215 jjdelcerro
        @Override
413 40435 jjdelcerro
        public String getDescription() {
414
            return "Evaluates join transform match";
415
        }
416
417 43215 jjdelcerro
        @Override
418 40435 jjdelcerro
        public String getName() {
419
            return "JoinTransformEvaluator";
420
        }
421
422 43215 jjdelcerro
        @Override
423 40435 jjdelcerro
        public EvaluatorFieldsInfo getFieldsInfo() {
424
            return this.info;
425
        }
426
427 43215 jjdelcerro
    }
428 40435 jjdelcerro
429
    @SuppressWarnings("unchecked")
430 43215 jjdelcerro
    @Override
431 40435 jjdelcerro
    public FeatureType getSourceFeatureTypeFrom(FeatureType arg0) {
432
        return originalFeatureType;
433
    }
434
435
    public static void registerPersistent() {
436
        PersistenceManager persistenceManager = ToolsLocator.getPersistenceManager();
437
438
        if( persistenceManager.getDefinition(AbstractFeatureStoreTransform.class) == null ) {
439
            AbstractFeatureStoreTransform.registerPersistent();
440
        }
441
442
        DynStruct definition = persistenceManager.getDefinition(PERSISTENCE_DEFINITION_NAME);
443
444 43215 jjdelcerro
        if( definition == null ) {
445 40435 jjdelcerro
            definition = persistenceManager.addDefinition(
446
                JoinTransform.class,
447
                PERSISTENCE_DEFINITION_NAME,
448
                "JoinTransform Persistence definition",
449 43215 jjdelcerro
                null,
450 40435 jjdelcerro
                null
451
            );
452
            definition.extend(PersistenceManager.PERSISTENCE_NAMESPACE,
453
                ABSTRACT_FEATURESTORE_DYNCLASS_NAME);
454
455
            definition.addDynFieldObject("store2").setClassOfValue(FeatureStore.class).setMandatory(true);
456
            definition.addDynFieldString("keyAttr1").setMandatory(true);
457
            definition.addDynFieldString("keyAttr2").setMandatory(true);
458
            definition.addDynFieldString("prefix1").setMandatory(false);
459
            definition.addDynFieldString("prefix2").setMandatory(false);
460
            definition.addDynFieldList("attrs").setClassOfItems(String.class).setMandatory(true);
461
        }
462
    }
463
464 43215 jjdelcerro
    @Override
465 40435 jjdelcerro
    public void saveToState(PersistentState state) throws PersistenceException {
466
        super.saveToState(state);
467
        state.set("store2", this.store2);
468
        state.set("keyAttr1", this.keyAttr1);
469
        state.set("keyAttr2", this.keyAttr2);
470
        state.set("prefix1", this.prefix1);
471 43215 jjdelcerro
        state.set("prefix2", this.prefix2);
472 40435 jjdelcerro
        state.set("attrs", this.attrs);
473
    }
474
475 43215 jjdelcerro
    @Override
476 40435 jjdelcerro
    public void loadFromState(PersistentState state) throws PersistenceException {
477
        super.loadFromState(state);
478 43215 jjdelcerro
        store2 = (FeatureStore) state.get("store2");
479
        keyAttr1 = state.getString("keyAttr1");
480
        keyAttr2 = state.getString("keyAttr2");
481
        prefix1 = state.getString("prefix1");
482
        prefix2 = state.getString("prefix2");
483
        attrs = (String[]) state.getArray("attrs", String.class);
484 40435 jjdelcerro
    }
485
486
}