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