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