Project

General

Profile

Download (15.3 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2007 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9
package eu.etaxonomy.taxeditor.bulkeditor.input;
10

    
11
import java.util.ArrayList;
12
import java.util.Collection;
13
import java.util.Comparator;
14
import java.util.HashMap;
15
import java.util.HashSet;
16
import java.util.List;
17
import java.util.Map;
18
import java.util.Map.Entry;
19
import java.util.Set;
20
import java.util.UUID;
21
import java.util.stream.Collectors;
22

    
23
import org.eclipse.core.runtime.ICoreRunnable;
24
import org.eclipse.core.runtime.jobs.Job;
25
import org.eclipse.jface.viewers.IStructuredSelection;
26

    
27
import ca.odell.glazedlists.BasicEventList;
28
import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
29
import eu.etaxonomy.cdm.api.service.config.DeleteConfiguratorBase;
30
import eu.etaxonomy.cdm.api.service.config.IIdentifiableEntityServiceConfigurator;
31
import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
32
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
33
import eu.etaxonomy.cdm.model.common.CdmBase;
34
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
35
import eu.etaxonomy.cdm.model.common.MarkerType;
36
import eu.etaxonomy.cdm.persistence.dto.MergeResult;
37
import eu.etaxonomy.cdm.strategy.merge.IMergable;
38
import eu.etaxonomy.cdm.strategy.merge.MergeException;
39
import eu.etaxonomy.taxeditor.annotatedlineeditor.IEntityCreator;
40
import eu.etaxonomy.taxeditor.annotatedlineeditor.IEntityPersistenceService;
41
import eu.etaxonomy.taxeditor.bulkeditor.BulkEditorQuery;
42
import eu.etaxonomy.taxeditor.bulkeditor.IBulkEditorSortProvider;
43
import eu.etaxonomy.taxeditor.bulkeditor.input.sortprovider.CdmBaseSortProvider;
44
import eu.etaxonomy.taxeditor.bulkeditor.input.sortprovider.TitleCacheComparator;
45
import eu.etaxonomy.taxeditor.bulkeditor.internal.TaxeditorBulkeditorPlugin;
46
import eu.etaxonomy.taxeditor.editor.CdmEntitySessionInput;
47
import eu.etaxonomy.taxeditor.event.EventUtility;
48
import eu.etaxonomy.taxeditor.event.WorkbenchEventConstants;
49
import eu.etaxonomy.taxeditor.l10n.Messages;
50
import eu.etaxonomy.taxeditor.model.MessagingUtils;
51
import eu.etaxonomy.taxeditor.store.CdmStore;
52

    
53
/**
54
 * @author p.ciardelli
55
 * @created 25.06.2009
56
 */
57
public abstract class AbstractBulkEditorInput<T extends CdmBase>
58
        extends CdmEntitySessionInput<T>
59
        implements IEntityPersistenceService<T> {
60

    
61
    private static final String PROPERTY_PROTECTED_TITLECACHE = "Protect TitleCache";
62
    private static final String TYPE_PROPERTY = Messages.BulkEditorE4_TYPE;
63
    private static final String ID_PROPERTY = "Id"; //$NON-NLS-1$
64
    private static final String UUID_PROPERTY = "Uuid"; //$NON-NLS-1$
65

    
66
	private UUID entityUuid;
67

    
68
	private BasicEventList<T> model = new BasicEventList<>();
69

    
70
	private Map<T, DeleteConfiguratorBase> toDelete = new HashMap<>();
71
	public Map<T, DeleteConfiguratorBase> getToDelete() {
72
        return toDelete;
73
    }
74

    
75
    private Set<T> saveCandidates = new HashSet<>();
76

    
77

    
78
	private Set<T> markedMergeCandidates = new HashSet<>();
79
	private T markedMergeTarget = null;
80

    
81
	private HashMap<T, Set<T>> mergedEntities = new HashMap<>();
82

    
83
	private IEntityCreator<T> entityCreator;
84
	private final ConversationHolder conversation;
85

    
86
    private Job searchJob;
87

    
88
	public AbstractBulkEditorInput() {
89
	    super(true);
90
	    this.conversation = CdmStore.createConversation();
91
	}
92

    
93
	static public AbstractBulkEditorInput<?> NewInstance(BulkEditorInputType inputType) {
94

    
95
		return BulkEditorInputType.getInput(inputType);
96
	}
97

    
98
	public static AbstractBulkEditorInput<?> NewInstance(IdentifiableEntity entity) {
99

    
100
		BulkEditorInputType inputType = BulkEditorInputType.getByType(entity.getClass());
101

    
102
		AbstractBulkEditorInput<?> editorInput = NewInstance(inputType);
103

    
104
		editorInput.setEntityUuid(entity.getUuid());
105

    
106
		return editorInput;
107
	}
108

    
109
	public static AbstractBulkEditorInput<?> NewInstance(Class clazz, UUID uuid) {
110

    
111
        BulkEditorInputType inputType = BulkEditorInputType.getByType(clazz);
112

    
113
        AbstractBulkEditorInput<?> editorInput = NewInstance(inputType);
114

    
115
        editorInput.setEntityUuid(uuid);
116

    
117
        return editorInput;
118
    }
119

    
120
    public abstract String getName();
121

    
122
    public String getEditorName(){
123
        return getName();
124
    }
125

    
126
    protected int getPageSize(){
127
        return 100;
128
    }
129

    
130
	protected abstract List<T> listEntities(IIdentifiableEntityServiceConfigurator configurator);
131

    
132
	protected abstract long countEntities(IIdentifiableEntityServiceConfigurator configurator);
133

    
134
	protected abstract T loadEntity(UUID entityUuid);
135

    
136
    public List<String> getPropertyKeys(){
137
        List<String> properties = new ArrayList<>();
138
        properties.add(getName());
139
        properties.add(PROPERTY_PROTECTED_TITLECACHE);
140
        properties.addAll(getPropertyKeys_internal());
141
        properties.add(TYPE_PROPERTY);
142
        properties.add(ID_PROPERTY);
143
        properties.add(UUID_PROPERTY);
144
        return properties;
145
    }
146

    
147
    protected abstract List<String> getPropertyKeys_internal();
148

    
149

    
150
    public Object getPropertyValue(T cdmBase, String property){
151
        if(property.equals(getName())){
152
            return getText(cdmBase);
153
        }
154
        else if(property.equals(PROPERTY_PROTECTED_TITLECACHE)
155
                &&cdmBase.isInstanceOf(IdentifiableEntity.class)){
156
            return HibernateProxyHelper.deproxy(cdmBase, IdentifiableEntity.class).isProtectedTitleCache();
157
        }
158
        else if(property.equals(TYPE_PROPERTY)){
159
            return getTypeText(cdmBase);
160
        }
161
        else if(property.equals(UUID_PROPERTY)){
162
            return cdmBase.getUuid();
163
        }
164
        else if(property.equals(ID_PROPERTY)){
165
            return cdmBase.getId();
166
        }
167
        return null;
168
    }
169

    
170
    public boolean isBooleanProperty(String property) {
171
        if(property.equals(PROPERTY_PROTECTED_TITLECACHE)){
172
            return true;
173
        }
174
        return false;
175
    }
176

    
177
    public boolean isCacheProperty(String property) {
178
        if(property.equals(PROPERTY_PROTECTED_TITLECACHE)){
179
            return true;
180
        }
181
        return false;
182
    }
183

    
184
	public <T extends IdentifiableEntity> Comparator<T> getTitleComparator(){
185
	    return new TitleCacheComparator<T>();
186
	}
187

    
188
	public void setMergeTarget(T t){
189
	    markedMergeTarget = t;
190
	}
191

    
192
    public Set<T> getMergeCandidates() {
193
        return markedMergeCandidates;
194
    }
195

    
196
    public T getMergeTarget() {
197
        return markedMergeTarget;
198
    }
199

    
200
	public void removeMergeTarget(){
201
	    markedMergeTarget = null;
202
	}
203

    
204
	public void addMergeCandidate(T t){
205
	    markedMergeCandidates.add(t);
206
	}
207

    
208
	public void removeMergeCandidate(T t){
209
		markedMergeCandidates.remove(t);
210
	}
211

    
212
    public void addToDelete(T t, DeleteConfiguratorBase config) {
213
        toDelete.put(t, config);
214
    }
215
    public void addSaveCandidate(T t){
216
        saveCandidates.add(t);
217
    }
218
	public void setEntityUuid(UUID entityUuid){
219
		this.entityUuid = entityUuid;
220
	}
221

    
222
	public UUID getEntityUuid() {
223
		return entityUuid;
224
	}
225

    
226
	public void performSearch(final BulkEditorQuery bulkEditorQuery, IStructuredSelection selection) {
227
	    //cancel previous search job
228
	    if(searchJob!=null && searchJob.getState()!=Job.NONE){
229
	        boolean isCanceled = searchJob.cancel();
230
	        if (!isCanceled){
231
	            while (!isCanceled){
232
    	            try {
233
    	                Thread.sleep(200);
234
    	            } catch (InterruptedException e) {
235
    	            }
236
    	            isCanceled = searchJob.cancel();
237
	            }
238
	        }
239
	        searchJob = null;
240
//	        /*
241
//	         * wait for a little while for the job to finish
242
//	         * to avoid asynchronously loaded results of the
243
//	         * previous search being shown in the next search
244
//	         * (not critical but explicitly waiting for the job to finish
245
//	         * could run into an endless loop by mistake)
246
//	         */
247
//	        try {
248
//                Thread.sleep(500);
249
//            } catch (InterruptedException e) {
250
//            }
251
	    }
252
        model.clear();
253
        markedMergeCandidates.clear();
254
        markedMergeTarget = null;
255

    
256
		if(getEntityUuid() != null){
257
			T entity = loadEntity(getEntityUuid());
258
			model.add(entity);
259
		}
260
		else if(bulkEditorQuery != null){
261
            IIdentifiableEntityServiceConfigurator<?> configurator = bulkEditorQuery.getSearchConfigurator();
262

    
263
            // check for UUID search
264
            String titleSearchString = configurator.getTitleSearchString();
265
            try {
266
                UUID uuid = UUID.fromString(titleSearchString);
267
                T entity = loadEntity(uuid);
268
                //UUID search found -> add entity to list and return
269
                model.add(entity);
270
                return;
271
            } catch (IllegalArgumentException e) {
272
                // search string was no UUID
273
            }
274
            //-> continue with standard search
275

    
276
            int pageSize = configurator.getPageSize()!=null?configurator.getPageSize():getPageSize();
277
            configurator.setPageSize(pageSize);
278
			long count = countEntities(configurator);
279
			int totalWork = count>Integer.MAX_VALUE?Integer.MAX_VALUE:(int)count;
280
			String jobLabel = String.format(Messages.AbstractBulkEditorInput_LOADING, getName(), bulkEditorQuery.getSearchString());
281
	        searchJob = Job.create(jobLabel, (ICoreRunnable) monitor -> {
282
	            monitor.beginTask(jobLabel, totalWork);
283
	            int pageNumber = 0;
284
	            List<T> entities;
285

    
286
	            //load previously selected element
287
	            UUID selectedUuid = null;
288
	            if(selection!=null && selection.getFirstElement() instanceof CdmBase){
289
	                selectedUuid = ((CdmBase) selection.getFirstElement()).getUuid();
290
	                T entity = loadEntity(selectedUuid);
291
	                model.add(entity);
292
	            }
293

    
294
                do {
295
                    if (monitor.isCanceled()) {
296
                        break;
297
                    }
298
                    configurator.setPageNumber(pageNumber);
299
                    entities = listEntities(configurator);
300

    
301
                    addToModel(entities, selectedUuid);
302

    
303
                    pageNumber++;
304
                    monitor.worked(pageSize);
305
                    long workedLong = pageSize*pageNumber;
306
                    int loadedCount =  workedLong>Integer.MAX_VALUE?Integer.MAX_VALUE:(int)workedLong;
307
                    monitor.setTaskName(String.format(Messages.AbstractBulkEditorInput_LOADED, loadedCount, totalWork, getName()));
308

    
309
                    //Update selection
310
                    EventUtility.postAsyncEvent(WorkbenchEventConstants.BULK_EDITOR_SEARCH_FINISHED, selection);
311
                } while (!entities.isEmpty());
312
	            monitor.done();
313
	            EventUtility.postAsyncEvent(WorkbenchEventConstants.BULK_EDITOR_SEARCH_FINISHED, selection);
314
	        });
315
	        searchJob.schedule();
316
		}
317
	}
318

    
319
    private void addToModel(List<T> entities, UUID selectedUuid){
320
	    //filter pre-loaded previously selected element
321
	    if(selectedUuid!=null){
322
	        entities = entities.stream().filter(entity->!entity.getUuid().equals(selectedUuid)).collect(Collectors.toList());
323
	    }
324
	    /*
325
         * IMPORTANT!
326
         * Entities have to be loaded into the main session because they are
327
         * loaded in a parallel asynchronous thread
328
         */
329
        if(getCdmEntitySession()!=null){//is null when closing the bulk editor during loading
330
            getCdmEntitySession().load(entities, true);
331
        }
332
        model.addAll(entities);
333
	}
334

    
335
	public boolean isMergingEnabled() {
336
		return false;
337
	}
338

    
339
	public boolean isConvertingEnabled() {
340
		return false;
341
	}
342

    
343
	public boolean isMarkerTypeEditingEnabled(MarkerType markerType) {
344
		return false;
345
	}
346

    
347
	@Override
348
    public boolean merge(T entity, T mergeTarget) {
349
		if (entity instanceof IMergable) {
350
			try {
351
				CdmStore.getCommonService().merge(mergeTarget.getUuid(), entity.getUuid(), (Class<? extends CdmBase>)entity.getClass());
352
			} catch (MergeException e) {
353
				MessagingUtils.errorDialog(Messages.AbstractBulkEditorInput_MERGE_ERROR_TITLE,
354
						this,
355
						String.format(Messages.AbstractBulkEditorInput_MERGE_ERROR_MESSAGE, entity.getClass().getName()),
356
						TaxeditorBulkeditorPlugin.PLUGIN_ID,
357
						e,
358
						true);
359
			}
360
		}
361
		return true;
362
	}
363

    
364
	public void saveModel(){
365
	    saveModel(true);
366
	}
367

    
368
	public void saveModel(boolean resetMerge){
369
	    //delete entities
370
	    for(Entry<T, DeleteConfiguratorBase> entry:toDelete.entrySet()){
371
	        try {
372
                delete(entry.getKey(), entry.getValue());
373
            } catch (ReferencedObjectUndeletableException e) {
374
                e.printStackTrace();
375
            }
376
	    }
377
	    if (!saveCandidates.isEmpty()){
378
	        List<MergeResult<T>> results = CdmStore.getService(saveCandidates.iterator().next()).merge(new ArrayList<>(saveCandidates), true);
379
	        for (MergeResult<T> result: results){
380
	            if (result.getMergedEntity() != null){
381
	                T entity = result.getMergedEntity();
382
	            }
383
	        }
384
        }
385
	    if(resetMerge){
386
	        //merge entities
387
	        for(T mergeTarget:mergedEntities.keySet()){
388
	            for (T mergeCandidate: mergedEntities.get(mergeTarget)){
389
	                merge(mergeCandidate, mergeTarget);
390
	            }
391
	        }
392
	    }
393
	    toDelete.clear();
394
	    saveCandidates.clear();
395
	    mergedEntities.clear();
396
	}
397

    
398
	@Override
399
    public T create(T entity) {
400
		return save(entity);
401
	}
402

    
403
	public IEntityCreator<T> getEntityCreator(){
404
		if(entityCreator == null){
405
			entityCreator = createEntityCreator();
406
		}
407
		return entityCreator;
408
	}
409

    
410
	protected abstract IEntityCreator<T> createEntityCreator();
411

    
412
	/**
413
	 * The default implementation returns an empty list of sort providers.
414
	 * @return
415
	 */
416
	public List<IBulkEditorSortProvider<T>> getSortProviders(){
417
		List<IBulkEditorSortProvider<T>> sortProviders = new ArrayList<>();
418

    
419
		sortProviders.add(new CdmBaseSortProvider<>());
420

    
421
		return sortProviders;
422
	}
423

    
424
	/**
425
	 * Returns a textual representation given object. The default implementation
426
	 * in the abstract base class returns the simple name of the class, this may
427
	 * be overwritten to something more specific in subclasses.
428
	 *
429
	 * @param entity
430
	 * @return a textual representation given object.
431
	 */
432
	public String getTypeText(Object entity){
433
		return entity.getClass().getSimpleName();
434
	}
435

    
436
	public String getText(T entity) {
437
		if(entity.isInstanceOf(IdentifiableEntity.class)){
438
			IdentifiableEntity<?> identifiableEntity = HibernateProxyHelper.deproxy(entity, IdentifiableEntity.class);
439
			String text = identifiableEntity.getTitleCache();
440
			return text;
441
		}
442

    
443
		return "No text. Implement in subclass"; //$NON-NLS-1$
444
	}
445

    
446
	public BasicEventList<T> getModel() {
447
		return model;
448
	}
449

    
450
	public boolean replaceInModel(T entity) {
451
	    int index = model.indexOf(entity);
452
	    if(index >= 0) {
453
	        model.set(index, entity);
454
	        return true;
455
	    } else {
456
	        return false;
457
	    }
458
	}
459

    
460
    @Override
461
    public Collection<T> getRootEntities() {
462
        return getModel();
463
    }
464

    
465
    @Override
466
    public Map<Object, List<String>> getPropertyPathsMap() {
467
        // TODO Auto-generated method stub
468
        return null;
469
    }
470

    
471
	public ConversationHolder getConversation() {
472
		return conversation;
473
	}
474

    
475
	public Set<T> getSaveCandidates() {
476
        return saveCandidates;
477
    }
478

    
479
    public HashMap<T, Set<T>> getMergedEntities() {
480
        return mergedEntities;
481
    }
482

    
483
    public void setMergedEntities(HashMap<T, Set<T>> mergedEntities) {
484
        this.mergedEntities = mergedEntities;
485
    }
486

    
487
}
(1-1/11)