2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
9 package eu
.etaxonomy
.taxeditor
.bulkeditor
.input
;
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
;
18 import java
.util
.Map
.Entry
;
20 import java
.util
.UUID
;
21 import java
.util
.stream
.Collectors
;
23 import org
.eclipse
.core
.runtime
.ICoreRunnable
;
24 import org
.eclipse
.core
.runtime
.jobs
.Job
;
25 import org
.eclipse
.jface
.viewers
.IStructuredSelection
;
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
;
57 public abstract class AbstractBulkEditorInput
<T
extends CdmBase
>
58 extends CdmEntitySessionInput
<T
>
59 implements IEntityPersistenceService
<T
> {
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$
66 private UUID entityUuid
;
68 private BasicEventList
<T
> model
= new BasicEventList
<>();
70 private Map
<T
, DeleteConfiguratorBase
> toDelete
= new HashMap
<>();
71 public Map
<T
, DeleteConfiguratorBase
> getToDelete() {
75 private Set
<T
> saveCandidates
= new HashSet
<>();
78 private Set
<T
> markedMergeCandidates
= new HashSet
<>();
79 private T markedMergeTarget
= null;
81 private HashMap
<T
, Set
<T
>> mergedEntities
= new HashMap
<>();
83 private IEntityCreator
<T
> entityCreator
;
84 private final ConversationHolder conversation
;
86 private Job searchJob
;
88 public AbstractBulkEditorInput() {
90 this.conversation
= CdmStore
.createConversation();
93 static public AbstractBulkEditorInput
<?
> NewInstance(BulkEditorInputType inputType
) {
95 return BulkEditorInputType
.getInput(inputType
);
98 public static AbstractBulkEditorInput
<?
> NewInstance(IdentifiableEntity entity
) {
100 BulkEditorInputType inputType
= BulkEditorInputType
.getByType(entity
.getClass());
102 AbstractBulkEditorInput
<?
> editorInput
= NewInstance(inputType
);
104 editorInput
.setEntityUuid(entity
.getUuid());
109 public static AbstractBulkEditorInput
<?
> NewInstance(Class clazz
, UUID uuid
) {
111 BulkEditorInputType inputType
= BulkEditorInputType
.getByType(clazz
);
113 AbstractBulkEditorInput
<?
> editorInput
= NewInstance(inputType
);
115 editorInput
.setEntityUuid(uuid
);
120 public abstract String
getName();
122 public String
getEditorName(){
126 protected int getPageSize(){
130 protected abstract List
<T
> listEntities(IIdentifiableEntityServiceConfigurator configurator
);
132 protected abstract long countEntities(IIdentifiableEntityServiceConfigurator configurator
);
134 protected abstract T
loadEntity(UUID entityUuid
);
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
);
147 protected abstract List
<String
> getPropertyKeys_internal();
150 public Object
getPropertyValue(T cdmBase
, String property
){
151 if(property
.equals(getName())){
152 return getText(cdmBase
);
154 else if(property
.equals(PROPERTY_PROTECTED_TITLECACHE
)
155 &&cdmBase
.isInstanceOf(IdentifiableEntity
.class)){
156 return HibernateProxyHelper
.deproxy(cdmBase
, IdentifiableEntity
.class).isProtectedTitleCache();
158 else if(property
.equals(TYPE_PROPERTY
)){
159 return getTypeText(cdmBase
);
161 else if(property
.equals(UUID_PROPERTY
)){
162 return cdmBase
.getUuid();
164 else if(property
.equals(ID_PROPERTY
)){
165 return cdmBase
.getId();
170 public boolean isBooleanProperty(String property
) {
171 if(property
.equals(PROPERTY_PROTECTED_TITLECACHE
)){
177 public boolean isCacheProperty(String property
) {
178 if(property
.equals(PROPERTY_PROTECTED_TITLECACHE
)){
184 public <T
extends IdentifiableEntity
> Comparator
<T
> getTitleComparator(){
185 return new TitleCacheComparator
<T
>();
188 public void setMergeTarget(T t
){
189 markedMergeTarget
= t
;
192 public Set
<T
> getMergeCandidates() {
193 return markedMergeCandidates
;
196 public T
getMergeTarget() {
197 return markedMergeTarget
;
200 public void removeMergeTarget(){
201 markedMergeTarget
= null;
204 public void addMergeCandidate(T t
){
205 markedMergeCandidates
.add(t
);
208 public void removeMergeCandidate(T t
){
209 markedMergeCandidates
.remove(t
);
212 public void addToDelete(T t
, DeleteConfiguratorBase config
) {
213 toDelete
.put(t
, config
);
215 public void addSaveCandidate(T t
){
216 saveCandidates
.add(t
);
218 public void setEntityUuid(UUID entityUuid
){
219 this.entityUuid
= entityUuid
;
222 public UUID
getEntityUuid() {
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();
234 } catch (InterruptedException e
) {
236 isCanceled
= searchJob
.cancel();
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)
248 // Thread.sleep(500);
249 // } catch (InterruptedException e) {
253 markedMergeCandidates
.clear();
254 markedMergeTarget
= null;
256 if(getEntityUuid() != null){
257 T entity
= loadEntity(getEntityUuid());
260 else if(bulkEditorQuery
!= null){
261 IIdentifiableEntityServiceConfigurator
<?
> configurator
= bulkEditorQuery
.getSearchConfigurator();
263 // check for UUID search
264 String titleSearchString
= configurator
.getTitleSearchString();
266 UUID uuid
= UUID
.fromString(titleSearchString
);
267 T entity
= loadEntity(uuid
);
268 //UUID search found -> add entity to list and return
271 } catch (IllegalArgumentException e
) {
272 // search string was no UUID
274 //-> continue with standard search
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
);
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
);
295 if (monitor
.isCanceled()) {
298 configurator
.setPageNumber(pageNumber
);
299 entities
= listEntities(configurator
);
301 addToModel(entities
, selectedUuid
);
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()));
310 EventUtility
.postAsyncEvent(WorkbenchEventConstants
.BULK_EDITOR_SEARCH_FINISHED
, selection
);
311 } while (!entities
.isEmpty());
313 EventUtility
.postAsyncEvent(WorkbenchEventConstants
.BULK_EDITOR_SEARCH_FINISHED
, selection
);
315 searchJob
.schedule();
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());
326 * Entities have to be loaded into the main session because they are
327 * loaded in a parallel asynchronous thread
329 if(getCdmEntitySession()!=null){//is null when closing the bulk editor during loading
330 getCdmEntitySession().load(entities
, true);
332 model
.addAll(entities
);
335 public boolean isMergingEnabled() {
339 public boolean isConvertingEnabled() {
343 public boolean isMarkerTypeEditingEnabled(MarkerType markerType
) {
348 public boolean merge(T entity
, T mergeTarget
) {
349 if (entity
instanceof IMergable
) {
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
,
355 String
.format(Messages
.AbstractBulkEditorInput_MERGE_ERROR_MESSAGE
, entity
.getClass().getName()),
356 TaxeditorBulkeditorPlugin
.PLUGIN_ID
,
364 public void saveModel(){
368 public void saveModel(boolean resetMerge
){
370 for(Entry
<T
, DeleteConfiguratorBase
> entry
:toDelete
.entrySet()){
372 delete(entry
.getKey(), entry
.getValue());
373 } catch (ReferencedObjectUndeletableException e
) {
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();
387 for(T mergeTarget
:mergedEntities
.keySet()){
388 for (T mergeCandidate
: mergedEntities
.get(mergeTarget
)){
389 merge(mergeCandidate
, mergeTarget
);
394 saveCandidates
.clear();
395 mergedEntities
.clear();
399 public T
create(T entity
) {
403 public IEntityCreator
<T
> getEntityCreator(){
404 if(entityCreator
== null){
405 entityCreator
= createEntityCreator();
407 return entityCreator
;
410 protected abstract IEntityCreator
<T
> createEntityCreator();
413 * The default implementation returns an empty list of sort providers.
416 public List
<IBulkEditorSortProvider
<T
>> getSortProviders(){
417 List
<IBulkEditorSortProvider
<T
>> sortProviders
= new ArrayList
<>();
419 sortProviders
.add(new CdmBaseSortProvider
<>());
421 return sortProviders
;
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.
430 * @return a textual representation given object.
432 public String
getTypeText(Object entity
){
433 return entity
.getClass().getSimpleName();
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();
443 return "No text. Implement in subclass"; //$NON-NLS-1$
446 public BasicEventList
<T
> getModel() {
450 public boolean replaceInModel(T entity
) {
451 int index
= model
.indexOf(entity
);
453 model
.set(index
, entity
);
461 public Collection
<T
> getRootEntities() {
466 public Map
<Object
, List
<String
>> getPropertyPathsMap() {
467 // TODO Auto-generated method stub
471 public ConversationHolder
getConversation() {
475 public Set
<T
> getSaveCandidates() {
476 return saveCandidates
;
479 public HashMap
<T
, Set
<T
>> getMergedEntities() {
480 return mergedEntities
;
483 public void setMergedEntities(HashMap
<T
, Set
<T
>> mergedEntities
) {
484 this.mergedEntities
= mergedEntities
;