Merge branch 'hotfix/5.18.2'
[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
59 implements IEntityPersistenceService<T> {
60
61
62 private static final String PROPERTY_PROTECTED_TITLECACHE = "Protect TitleCache";
63 private static final String TYPE_PROPERTY = Messages.BulkEditorE4_TYPE;
64 private static final String ID_PROPERTY = "Id"; //$NON-NLS-1$
65 private static final String UUID_PROPERTY = "Uuid"; //$NON-NLS-1$
66
67 private UUID entityUuid;
68
69 private BasicEventList<T> model = new BasicEventList<>();
70
71 private Map<T, DeleteConfiguratorBase> toDelete = new HashMap<>();
72 public Map<T, DeleteConfiguratorBase> getToDelete() {
73 return toDelete;
74 }
75
76 private Set<T> saveCandidates = new HashSet<>();
77
78
79 private Set<T> markedMergeCandidates = new HashSet<>();
80 private T markedMergeTarget = null;
81
82 private HashMap<T, Set<T>> mergedEntities = new HashMap<>();
83
84 private IEntityCreator<T> entityCreator;
85 private final ConversationHolder conversation;
86
87 private Job searchJob;
88
89 public AbstractBulkEditorInput() {
90 super(true);
91 this.conversation = CdmStore.createConversation();
92 }
93
94 static public AbstractBulkEditorInput NewInstance(BulkEditorInputType inputType) {
95
96 return BulkEditorInputType.getInput(inputType);
97 }
98
99 public static AbstractBulkEditorInput NewInstance(IdentifiableEntity entity) {
100
101 BulkEditorInputType inputType = BulkEditorInputType.getByType(entity.getClass());
102
103 AbstractBulkEditorInput<?> editorInput = NewInstance(inputType);
104
105 editorInput.setEntityUuid(entity.getUuid());
106
107 return editorInput;
108 }
109
110 public static AbstractBulkEditorInput NewInstance(Class clazz, UUID uuid) {
111
112 BulkEditorInputType inputType = BulkEditorInputType.getByType(clazz);
113
114 AbstractBulkEditorInput<?> editorInput = NewInstance(inputType);
115
116 editorInput.setEntityUuid(uuid);
117
118 return editorInput;
119 }
120
121 public abstract String getName();
122
123 public String getEditorName(){
124 return getName();
125 }
126
127 protected int getPageSize(){
128 return 100;
129 }
130
131 protected abstract List<T> listEntities(IIdentifiableEntityServiceConfigurator configurator);
132
133 protected abstract long countEntities(IIdentifiableEntityServiceConfigurator configurator);
134
135 protected abstract T loadEntity(UUID entityUuid);
136
137 public List<String> getPropertyKeys(){
138 List<String> properties = new ArrayList<>();
139 properties.add(getName());
140 properties.add(PROPERTY_PROTECTED_TITLECACHE);
141 properties.addAll(getPropertyKeys_internal());
142 properties.add(TYPE_PROPERTY);
143 properties.add(ID_PROPERTY);
144 properties.add(UUID_PROPERTY);
145 return properties;
146 }
147
148 protected abstract List<String> getPropertyKeys_internal();
149
150
151 public Object getPropertyValue(T cdmBase, String property){
152 if(property.equals(getName())){
153 return getText(cdmBase);
154 }
155 else if(property.equals(PROPERTY_PROTECTED_TITLECACHE)
156 &&cdmBase.isInstanceOf(IdentifiableEntity.class)){
157 return HibernateProxyHelper.deproxy(cdmBase, IdentifiableEntity.class).isProtectedTitleCache();
158 }
159 else if(property.equals(TYPE_PROPERTY)){
160 return getTypeText(cdmBase);
161 }
162 else if(property.equals(UUID_PROPERTY)){
163 return cdmBase.getUuid();
164 }
165 else if(property.equals(ID_PROPERTY)){
166 return cdmBase.getId();
167 }
168 return null;
169 }
170
171 public boolean isBooleanProperty(String property) {
172 if(property.equals(PROPERTY_PROTECTED_TITLECACHE)){
173 return true;
174 }
175 return false;
176 }
177
178 public boolean isCacheProperty(String property) {
179 if(property.equals(PROPERTY_PROTECTED_TITLECACHE)){
180 return true;
181 }
182 return false;
183 }
184
185 public <T extends IdentifiableEntity> Comparator<T> getTitleComparator(){
186 return new TitleCacheComparator<T>();
187 }
188
189 public void setMergeTarget(T t){
190 markedMergeTarget = t;
191 }
192
193 public Set<T> getMergeCandidates() {
194 return markedMergeCandidates;
195 }
196
197 public T getMergeTarget() {
198 return markedMergeTarget;
199 }
200
201 public void removeMergeTarget(){
202 markedMergeTarget = null;
203 }
204
205 public void addMergeCandidate(T t){
206 markedMergeCandidates.add(t);
207 }
208
209 public void removeMergeCandidate(T t){
210 markedMergeCandidates.remove(t);
211 }
212
213 public void addToDelete(T t, DeleteConfiguratorBase config) {
214 toDelete.put(t, config);
215 }
216 public void addSaveCandidate(T t){
217 saveCandidates.add(t);
218 }
219 public void setEntityUuid(UUID entityUuid){
220 this.entityUuid = entityUuid;
221 }
222
223 public UUID getEntityUuid() {
224 return entityUuid;
225 }
226
227 public void performSearch(final BulkEditorQuery bulkEditorQuery, IStructuredSelection selection) {
228 //cancel previous search job
229 if(searchJob!=null && searchJob.getState()!=Job.NONE){
230 searchJob.cancel();
231 searchJob = null;
232 /*
233 * wait for a little while for the job to finish
234 * to avoid asynchronously loaded results of the
235 * previous search being shown in the next search
236 * (not critical but explicitly waiting for the job to finish
237 * could run into an endless loop by mistake)
238 */
239 try {
240 Thread.sleep(500);
241 } catch (InterruptedException e) {
242 }
243 }
244 model.clear();
245 markedMergeCandidates.clear();
246 markedMergeTarget = null;
247
248 if(getEntityUuid() != null){
249 T entity = loadEntity(getEntityUuid());
250 model.add(entity);
251 }
252 else if(bulkEditorQuery != null){
253 IIdentifiableEntityServiceConfigurator<?> configurator = bulkEditorQuery.getSearchConfigurator();
254
255 // check for UUID search
256 String titleSearchString = configurator.getTitleSearchString();
257 try {
258 UUID uuid = UUID.fromString(titleSearchString);
259 T entity = loadEntity(uuid);
260 //UUID search found -> add entity to list and return
261 model.add(entity);
262 return;
263 } catch (IllegalArgumentException e) {
264 // search string was no UUID
265 }
266 //-> continue with standard search
267
268 int pageSize = configurator.getPageSize()!=null?configurator.getPageSize():getPageSize();
269 configurator.setPageSize(pageSize);
270 long count = countEntities(configurator);
271 int totalWork = count>Integer.MAX_VALUE?Integer.MAX_VALUE:(int)count;
272 String jobLabel = String.format(Messages.AbstractBulkEditorInput_LOADING, getName(), bulkEditorQuery.getSearchString());
273 searchJob = Job.create(jobLabel, (ICoreRunnable) monitor -> {
274 monitor.beginTask(jobLabel, totalWork);
275 int pageNumber = 0;
276 List<T> entities;
277
278 //load previously selected element
279 UUID selectedUuid = null;
280 if(selection!=null && selection.getFirstElement() instanceof CdmBase){
281 selectedUuid = ((CdmBase) selection.getFirstElement()).getUuid();
282 T entity = loadEntity(selectedUuid);
283 model.add(entity);
284 }
285
286 do {
287 if (monitor.isCanceled()) {
288 break;
289 }
290 configurator.setPageNumber(pageNumber);
291 entities = listEntities(configurator);
292
293 addToModel(entities, selectedUuid);
294
295 pageNumber++;
296 monitor.worked(pageSize);
297 long workedLong = pageSize*pageNumber;
298 int loadedCount = workedLong>Integer.MAX_VALUE?Integer.MAX_VALUE:(int)workedLong;
299 monitor.setTaskName(String.format(Messages.AbstractBulkEditorInput_LOADED, loadedCount, totalWork, getName()));
300
301 //Update selection
302 EventUtility.postAsyncEvent(WorkbenchEventConstants.BULK_EDITOR_SEARCH_FINISHED, selection);
303 } while (!entities.isEmpty());
304 monitor.done();
305 EventUtility.postAsyncEvent(WorkbenchEventConstants.BULK_EDITOR_SEARCH_FINISHED, selection);
306 });
307 searchJob.schedule();
308 }
309 }
310
311 private void addToModel(List<T> entities, UUID selectedUuid){
312 //filter pre-loaded previously selected element
313 if(selectedUuid!=null){
314 entities = entities.stream().filter(entity->!entity.getUuid().equals(selectedUuid)).collect(Collectors.toList());
315 }
316 /*
317 * IMPORTANT!
318 * Entities have to be loaded into the main session because they are
319 * loaded in a parallel asynchronous thread
320 */
321 if(getCdmEntitySession()!=null){//is null when closing the bulk editor during loading
322 getCdmEntitySession().load(entities, true);
323 }
324 model.addAll(entities);
325 }
326
327 public boolean isMergingEnabled() {
328 return false;
329 }
330
331 public boolean isConvertingEnabled() {
332 return false;
333 }
334
335 public boolean isMarkerTypeEditingEnabled(MarkerType markerType) {
336 return false;
337 }
338
339 @Override
340 public boolean merge(T entity, T mergeTarget) {
341 if (entity instanceof IMergable) {
342 try {
343 CdmStore.getCommonService().merge(mergeTarget.getUuid(), entity.getUuid(), (Class<? extends CdmBase>)entity.getClass());
344 } catch (MergeException e) {
345 MessagingUtils.errorDialog(Messages.AbstractBulkEditorInput_MERGE_ERROR_TITLE,
346 this,
347 String.format(Messages.AbstractBulkEditorInput_MERGE_ERROR_MESSAGE, entity.getClass().getName()),
348 TaxeditorBulkeditorPlugin.PLUGIN_ID,
349 e,
350 true);
351 }
352 }
353 return true;
354 }
355
356 public void saveModel(){
357 saveModel(true);
358 }
359
360 public void saveModel(boolean resetMerge){
361 //delete entities
362 for(Entry<T, DeleteConfiguratorBase> entry:toDelete.entrySet()){
363 try {
364 delete(entry.getKey(), entry.getValue());
365 } catch (ReferencedObjectUndeletableException e) {
366 e.printStackTrace();
367 }
368 }
369 if (!saveCandidates.isEmpty()){
370 List<MergeResult<T>> results = CdmStore.getService(saveCandidates.iterator().next()).merge(new ArrayList<>(saveCandidates), true);
371 for (MergeResult<T> result: results){
372 if (result.getMergedEntity() != null){
373 T entity = result.getMergedEntity();
374 }
375 }
376 }
377 if(resetMerge){
378 //merge entities
379 for(T mergeTarget:mergedEntities.keySet()){
380 for (T mergeCandidate: mergedEntities.get(mergeTarget)){
381 merge(mergeCandidate, mergeTarget);
382 }
383 }
384 }
385 toDelete.clear();
386 saveCandidates.clear();
387 mergedEntities.clear();
388 }
389
390 @Override
391 public T create(T entity) {
392 return save(entity);
393 }
394
395 public IEntityCreator<T> getEntityCreator(){
396 if(entityCreator == null){
397 entityCreator = createEntityCreator();
398 }
399 return entityCreator;
400 }
401
402 protected abstract IEntityCreator<T> createEntityCreator();
403
404 /**
405 * The default implementation returns an empty list of sort providers.
406 * @return
407 */
408 public List<IBulkEditorSortProvider<T>> getSortProviders(){
409 List<IBulkEditorSortProvider<T>> sortProviders = new ArrayList<>();
410
411 sortProviders.add(new CdmBaseSortProvider<>());
412
413 return sortProviders;
414 }
415
416 /**
417 * Returns a textual representation given object. The default implementation
418 * in the abstract base class returns the simple name of the class, this may
419 * be overwritten to something more specific in subclasses.
420 *
421 * @param entity
422 * @return a textual representation given object.
423 */
424 public String getTypeText(Object entity){
425 return entity.getClass().getSimpleName();
426 }
427
428 public String getText(T entity) {
429 if(entity.isInstanceOf(IdentifiableEntity.class)){
430 IdentifiableEntity<?> identifiableEntity = HibernateProxyHelper.deproxy(entity, IdentifiableEntity.class);
431 String text = identifiableEntity.getTitleCache();
432 return text;
433 }
434
435 return "No text. Implement in subclass"; //$NON-NLS-1$
436 }
437
438 public BasicEventList<T> getModel() {
439 return model;
440 }
441
442 public boolean replaceInModel(T entity) {
443 int index = model.indexOf(entity);
444 if(index >= 0) {
445 model.set(index, entity);
446 return true;
447 } else {
448 return false;
449 }
450 }
451
452 @Override
453 public Collection<T> getRootEntities() {
454 return getModel();
455 }
456
457
458 @Override
459 public Map<Object, List<String>> getPropertyPathsMap() {
460 // TODO Auto-generated method stub
461 return null;
462 }
463
464 public ConversationHolder getConversation() {
465 return conversation;
466 }
467
468 public Set<T> getSaveCandidates() {
469 return saveCandidates;
470 }
471
472 public HashMap<T, Set<T>> getMergedEntities() {
473 return mergedEntities;
474 }
475
476 public void setMergedEntities(HashMap<T, Set<T>> mergedEntities) {
477 this.mergedEntities = mergedEntities;
478 }
479
480 }