Merge branch 'release/5.19.0'
[taxeditor.git] / eu.etaxonomy.taxeditor.bulkeditor / src / main / java / eu / etaxonomy / taxeditor / bulkeditor / input / AbstractBulkEditorInput.java
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 }