ref #7699: wait for the thread to stop
[taxeditor.git] / eu.etaxonomy.taxeditor.bulkeditor / src / main / java / eu / etaxonomy / taxeditor / bulkeditor / input / AbstractBulkEditorInput.java
index d57ea686635b84c2219f18ff87d83e6e1c1873d6..747dc3df644b3d366599870eb49a169cda42dc6f 100644 (file)
@@ -1,4 +1,3 @@
-// $Id$
 /**
 * Copyright (C) 2007 EDIT
 * European Distributed Institute of Taxonomy
 package eu.etaxonomy.taxeditor.bulkeditor.input;
 
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Collection;
 import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 import java.util.UUID;
+import java.util.stream.Collectors;
 
-import org.eclipse.jface.resource.ImageDescriptor;
-import org.eclipse.ui.IEditorInput;
-import org.eclipse.ui.IPersistableElement;
+import org.eclipse.core.runtime.ICoreRunnable;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.viewers.IStructuredSelection;
 
+import ca.odell.glazedlists.BasicEventList;
 import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
+import eu.etaxonomy.cdm.api.service.config.DeleteConfiguratorBase;
 import eu.etaxonomy.cdm.api.service.config.IIdentifiableEntityServiceConfigurator;
+import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
 import eu.etaxonomy.cdm.model.common.CdmBase;
-import eu.etaxonomy.cdm.model.common.ICdmBase;
 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
 import eu.etaxonomy.cdm.model.common.MarkerType;
+import eu.etaxonomy.cdm.persistence.dto.MergeResult;
 import eu.etaxonomy.cdm.strategy.merge.IMergable;
 import eu.etaxonomy.cdm.strategy.merge.MergeException;
 import eu.etaxonomy.taxeditor.annotatedlineeditor.IEntityCreator;
@@ -37,25 +44,48 @@ import eu.etaxonomy.taxeditor.bulkeditor.input.sortprovider.CdmBaseSortProvider;
 import eu.etaxonomy.taxeditor.bulkeditor.input.sortprovider.TitleCacheComparator;
 import eu.etaxonomy.taxeditor.bulkeditor.internal.TaxeditorBulkeditorPlugin;
 import eu.etaxonomy.taxeditor.editor.CdmEntitySessionInput;
+import eu.etaxonomy.taxeditor.event.EventUtility;
+import eu.etaxonomy.taxeditor.event.WorkbenchEventConstants;
+import eu.etaxonomy.taxeditor.l10n.Messages;
 import eu.etaxonomy.taxeditor.model.MessagingUtils;
 import eu.etaxonomy.taxeditor.store.CdmStore;
 
 /**
  * @author p.ciardelli
  * @created 25.06.2009
- * @version 1.0
- * @param <T>
  */
-public abstract class AbstractBulkEditorInput<T extends ICdmBase> extends CdmEntitySessionInput implements IEditorInput ,
-    IEntityPersistenceService<T> {
+public abstract class AbstractBulkEditorInput<T extends CdmBase>
+        extends CdmEntitySessionInput
+        implements IEntityPersistenceService<T> {
+
+
+    private static final String PROPERTY_PROTECTED_TITLECACHE = "Protect TitleCache";
+    private static final String TYPE_PROPERTY = Messages.BulkEditorE4_TYPE;
+    private static final String ID_PROPERTY = "Id"; //$NON-NLS-1$
+    private static final String UUID_PROPERTY = "Uuid"; //$NON-NLS-1$
 
        private UUID entityUuid;
 
-       private List<T> model;
+       private BasicEventList<T> model = new BasicEventList<>();
+
+       private Map<T, DeleteConfiguratorBase> toDelete = new HashMap<>();
+       public Map<T, DeleteConfiguratorBase> getToDelete() {
+        return toDelete;
+    }
+
+    private Set<T> saveCandidates = new HashSet<>();
+
+
+       private Set<T> markedMergeCandidates = new HashSet<>();
+       private T markedMergeTarget = null;
+
+       private HashMap<T, Set<T>> mergedEntities = new HashMap<>();
 
        private IEntityCreator<T> entityCreator;
        private final ConversationHolder conversation;
 
+    private Job searchJob;
+
        public AbstractBulkEditorInput() {
            super(true);
            this.conversation = CdmStore.createConversation();
@@ -70,72 +100,237 @@ public abstract class AbstractBulkEditorInput<T extends ICdmBase> extends CdmEnt
 
                BulkEditorInputType inputType = BulkEditorInputType.getByType(entity.getClass());
 
-               AbstractBulkEditorInput editorInput = NewInstance(inputType);
+               AbstractBulkEditorInput<?> editorInput = NewInstance(inputType);
 
                editorInput.setEntityUuid(entity.getUuid());
 
                return editorInput;
        }
 
+       public static AbstractBulkEditorInput NewInstance(Class clazz, UUID uuid) {
+
+        BulkEditorInputType inputType = BulkEditorInputType.getByType(clazz);
+
+        AbstractBulkEditorInput<?> editorInput = NewInstance(inputType);
+
+        editorInput.setEntityUuid(uuid);
+
+        return editorInput;
+    }
+
+    public abstract String getName();
+
+    public String getEditorName(){
+        return getName();
+    }
+
+    protected int getPageSize(){
+        return 100;
+    }
+
        protected abstract List<T> listEntities(IIdentifiableEntityServiceConfigurator configurator);
 
+       protected abstract long countEntities(IIdentifiableEntityServiceConfigurator configurator);
+
        protected abstract T loadEntity(UUID entityUuid);
 
-       private void setEntityUuid(UUID entityUuid){
-               this.entityUuid = entityUuid;
+    public List<String> getPropertyKeys(){
+        List<String> properties = new ArrayList<>();
+        properties.add(getName());
+        properties.add(PROPERTY_PROTECTED_TITLECACHE);
+        properties.addAll(getPropertyKeys_internal());
+        properties.add(TYPE_PROPERTY);
+        properties.add(ID_PROPERTY);
+        properties.add(UUID_PROPERTY);
+        return properties;
+    }
+
+    protected abstract List<String> getPropertyKeys_internal();
+
+
+    public Object getPropertyValue(T cdmBase, String property){
+        if(property.equals(getName())){
+            return getText(cdmBase);
+        }
+        else if(property.equals(PROPERTY_PROTECTED_TITLECACHE)
+                &&cdmBase.isInstanceOf(IdentifiableEntity.class)){
+            return HibernateProxyHelper.deproxy(cdmBase, IdentifiableEntity.class).isProtectedTitleCache();
+        }
+        else if(property.equals(TYPE_PROPERTY)){
+            return getTypeText(cdmBase);
+        }
+        else if(property.equals(UUID_PROPERTY)){
+            return cdmBase.getUuid();
+        }
+        else if(property.equals(ID_PROPERTY)){
+            return cdmBase.getId();
+        }
+        return null;
+    }
+
+    public boolean isBooleanProperty(String property) {
+        if(property.equals(PROPERTY_PROTECTED_TITLECACHE)){
+            return true;
+        }
+        return false;
+    }
+
+    public boolean isCacheProperty(String property) {
+        if(property.equals(PROPERTY_PROTECTED_TITLECACHE)){
+            return true;
+        }
+        return false;
+    }
+
+       public <T extends IdentifiableEntity> Comparator<T> getTitleComparator(){
+           return new TitleCacheComparator<T>();
        }
 
-       public UUID getEntityUuid() {
-               return entityUuid;
+       public void setMergeTarget(T t){
+           markedMergeTarget = t;
        }
 
-       @Override
-    public boolean exists() {
-               // TODO Auto-generated method stub
-               return false;
+    public Set<T> getMergeCandidates() {
+        return markedMergeCandidates;
+    }
+
+    public T getMergeTarget() {
+        return markedMergeTarget;
+    }
+
+       public void removeMergeTarget(){
+           markedMergeTarget = null;
        }
 
-       @Override
-    public ImageDescriptor getImageDescriptor() {
-               // TODO Auto-generated method stub
-               return null;
+       public void addMergeCandidate(T t){
+           markedMergeCandidates.add(t);
        }
 
-       @Override
-    public IPersistableElement getPersistable() {
-               return null;
+       public void removeMergeCandidate(T t){
+               markedMergeCandidates.remove(t);
        }
 
-       /** {@inheritDoc} */
-       @Override
-    @SuppressWarnings("unchecked")
-       public Object getAdapter(Class adapter) {
-               return null;
+    public void addToDelete(T t, DeleteConfiguratorBase config) {
+        toDelete.put(t, config);
+    }
+    public void addSaveCandidate(T t){
+        saveCandidates.add(t);
+    }
+       public void setEntityUuid(UUID entityUuid){
+               this.entityUuid = entityUuid;
        }
 
-       public void performSearch(final BulkEditorQuery bulkEditorQuery) {
+       public UUID getEntityUuid() {
+               return entityUuid;
+       }
 
-               List<T> entityList = new ArrayList<T>();
+       public void performSearch(final BulkEditorQuery bulkEditorQuery, IStructuredSelection selection) {
+           //cancel previous search job
+           if(searchJob!=null && searchJob.getState()!=Job.NONE){
+               boolean isCanceled = searchJob.cancel();
+               if (!isCanceled){
+                   while (!isCanceled){
+                   try {
+                       Thread.sleep(200);
+                   } catch (InterruptedException e) {
+                   }
+                   isCanceled = searchJob.cancel();
+                   }
+               }
+               searchJob = null;
+//             /*
+//              * wait for a little while for the job to finish
+//              * to avoid asynchronously loaded results of the
+//              * previous search being shown in the next search
+//              * (not critical but explicitly waiting for the job to finish
+//              * could run into an endless loop by mistake)
+//              */
+//             try {
+//                Thread.sleep(500);
+//            } catch (InterruptedException e) {
+//            }
+           }
+        model.clear();
+        markedMergeCandidates.clear();
+        markedMergeTarget = null;
 
                if(getEntityUuid() != null){
-
                        T entity = loadEntity(getEntityUuid());
-                       entityList.add(entity);
-                       model = entityList;
+                       model.add(entity);
                }
                else if(bulkEditorQuery != null){
-
-                       IIdentifiableEntityServiceConfigurator configurator = bulkEditorQuery.getSearchConfigurator();
-                       Comparator queryComparator = (bulkEditorQuery.getComparator() != null) ? bulkEditorQuery.getComparator() : new TitleCacheComparator();
-
-                       entityList = listEntities(configurator);
-
-                       Collections.sort(entityList, queryComparator);
-
-
+            IIdentifiableEntityServiceConfigurator<?> configurator = bulkEditorQuery.getSearchConfigurator();
+
+            // check for UUID search
+            String titleSearchString = configurator.getTitleSearchString();
+            try {
+                UUID uuid = UUID.fromString(titleSearchString);
+                T entity = loadEntity(uuid);
+                //UUID search found -> add entity to list and return
+                model.add(entity);
+                return;
+            } catch (IllegalArgumentException e) {
+                // search string was no UUID
+            }
+            //-> continue with standard search
+
+            int pageSize = configurator.getPageSize()!=null?configurator.getPageSize():getPageSize();
+            configurator.setPageSize(pageSize);
+                       long count = countEntities(configurator);
+                       int totalWork = count>Integer.MAX_VALUE?Integer.MAX_VALUE:(int)count;
+                       String jobLabel = String.format(Messages.AbstractBulkEditorInput_LOADING, getName(), bulkEditorQuery.getSearchString());
+               searchJob = Job.create(jobLabel, (ICoreRunnable) monitor -> {
+                   monitor.beginTask(jobLabel, totalWork);
+                   int pageNumber = 0;
+                   List<T> entities;
+
+                   //load previously selected element
+                   UUID selectedUuid = null;
+                   if(selection!=null && selection.getFirstElement() instanceof CdmBase){
+                       selectedUuid = ((CdmBase) selection.getFirstElement()).getUuid();
+                       T entity = loadEntity(selectedUuid);
+                       model.add(entity);
+                   }
+
+                do {
+                    if (monitor.isCanceled()) {
+                        break;
+                    }
+                    configurator.setPageNumber(pageNumber);
+                    entities = listEntities(configurator);
+
+                    addToModel(entities, selectedUuid);
+
+                    pageNumber++;
+                    monitor.worked(pageSize);
+                    long workedLong = pageSize*pageNumber;
+                    int loadedCount =  workedLong>Integer.MAX_VALUE?Integer.MAX_VALUE:(int)workedLong;
+                    monitor.setTaskName(String.format(Messages.AbstractBulkEditorInput_LOADED, loadedCount, totalWork, getName()));
+
+                    //Update selection
+                    EventUtility.postAsyncEvent(WorkbenchEventConstants.BULK_EDITOR_SEARCH_FINISHED, selection);
+                } while (!entities.isEmpty());
+                   monitor.done();
+                   EventUtility.postAsyncEvent(WorkbenchEventConstants.BULK_EDITOR_SEARCH_FINISHED, selection);
+               });
+               searchJob.schedule();
                }
+       }
 
-               model = entityList;
+    private void addToModel(List<T> entities, UUID selectedUuid){
+           //filter pre-loaded previously selected element
+           if(selectedUuid!=null){
+               entities = entities.stream().filter(entity->!entity.getUuid().equals(selectedUuid)).collect(Collectors.toList());
+           }
+           /*
+         * IMPORTANT!
+         * Entities have to be loaded into the main session because they are
+         * loaded in a parallel asynchronous thread
+         */
+        if(getCdmEntitySession()!=null){//is null when closing the bulk editor during loading
+            getCdmEntitySession().load(entities, true);
+        }
+        model.addAll(entities);
        }
 
        public boolean isMergingEnabled() {
@@ -150,17 +345,15 @@ public abstract class AbstractBulkEditorInput<T extends ICdmBase> extends CdmEnt
                return false;
        }
 
-
-       /** {@inheritDoc} */
        @Override
     public boolean merge(T entity, T mergeTarget) {
                if (entity instanceof IMergable) {
                        try {
                                CdmStore.getCommonService().merge(mergeTarget.getUuid(), entity.getUuid(), (Class<? extends CdmBase>)entity.getClass());
                        } catch (MergeException e) {
-                               MessagingUtils.errorDialog("Bulk Editor Merge Error",
+                               MessagingUtils.errorDialog(Messages.AbstractBulkEditorInput_MERGE_ERROR_TITLE,
                                                this,
-                                               "Could not merge chosen objects of type " + entity.getClass().getName(),
+                                               String.format(Messages.AbstractBulkEditorInput_MERGE_ERROR_MESSAGE, entity.getClass().getName()),
                                                TaxeditorBulkeditorPlugin.PLUGIN_ID,
                                                e,
                                                true);
@@ -169,8 +362,40 @@ public abstract class AbstractBulkEditorInput<T extends ICdmBase> extends CdmEnt
                return true;
        }
 
+       public void saveModel(){
+           saveModel(true);
+       }
+
+       public void saveModel(boolean resetMerge){
+           //delete entities
+           for(Entry<T, DeleteConfiguratorBase> entry:toDelete.entrySet()){
+               try {
+                delete(entry.getKey(), entry.getValue());
+            } catch (ReferencedObjectUndeletableException e) {
+                e.printStackTrace();
+            }
+           }
+           if (!saveCandidates.isEmpty()){
+               List<MergeResult<T>> results = CdmStore.getService(saveCandidates.iterator().next()).merge(new ArrayList<>(saveCandidates), true);
+               for (MergeResult<T> result: results){
+                   if (result.getMergedEntity() != null){
+                       T entity = result.getMergedEntity();
+                   }
+               }
+        }
+           if(resetMerge){
+               //merge entities
+               for(T mergeTarget:mergedEntities.keySet()){
+                   for (T mergeCandidate: mergedEntities.get(mergeTarget)){
+                       merge(mergeCandidate, mergeTarget);
+                   }
+               }
+           }
+           toDelete.clear();
+           saveCandidates.clear();
+           mergedEntities.clear();
+       }
 
-       /** {@inheritDoc} */
        @Override
     public T create(T entity) {
                return save(entity);
@@ -190,9 +415,9 @@ public abstract class AbstractBulkEditorInput<T extends ICdmBase> extends CdmEnt
         * @return
         */
        public List<IBulkEditorSortProvider<T>> getSortProviders(){
-               List<IBulkEditorSortProvider<T>> sortProviders = new ArrayList<IBulkEditorSortProvider<T>>();
+               List<IBulkEditorSortProvider<T>> sortProviders = new ArrayList<>();
 
-               sortProviders.add(new CdmBaseSortProvider<T>());
+               sortProviders.add(new CdmBaseSortProvider<>());
 
                return sortProviders;
        }
@@ -210,20 +435,20 @@ public abstract class AbstractBulkEditorInput<T extends ICdmBase> extends CdmEnt
        }
 
        public String getText(T entity) {
-               if(entity instanceof IdentifiableEntity){
-                       IdentifiableEntity identifiableEntity = (IdentifiableEntity) HibernateProxyHelper.deproxy(entity);
-
-                       return identifiableEntity.getTitleCache();
+               if(entity.isInstanceOf(IdentifiableEntity.class)){
+                       IdentifiableEntity<?> identifiableEntity = HibernateProxyHelper.deproxy(entity, IdentifiableEntity.class);
+                       String text = identifiableEntity.getTitleCache();
+                       return text;
                }
 
-               return "No text. Implement in subclass";
+               return "No text. Implement in subclass"; //$NON-NLS-1$
        }
 
-       public List<T> getModel() {
+       public BasicEventList<T> getModel() {
                return model;
        }
 
-       protected boolean replaceInModel(T entity) {
+       public boolean replaceInModel(T entity) {
            int index = model.indexOf(entity);
            if(index >= 0) {
                model.set(index, entity);
@@ -234,7 +459,7 @@ public abstract class AbstractBulkEditorInput<T extends ICdmBase> extends CdmEnt
        }
 
     @Override
-    public   List<T> getRootEntities() {
+    public Collection<T> getRootEntities() {
         return getModel();
     }
 
@@ -248,4 +473,17 @@ public abstract class AbstractBulkEditorInput<T extends ICdmBase> extends CdmEnt
        public ConversationHolder getConversation() {
                return conversation;
        }
+
+       public Set<T> getSaveCandidates() {
+        return saveCandidates;
+    }
+
+    public HashMap<T, Set<T>> getMergedEntities() {
+        return mergedEntities;
+    }
+
+    public void setMergedEntities(HashMap<T, Set<T>> mergedEntities) {
+        this.mergedEntities = mergedEntities;
+    }
+
 }