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