ref #9607 replacing class local factory methods and heplers in CdmImageInfo by servic...
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / MediaServiceImpl.java
index b8c6a73000ab6bf9b9bc826f18d85dc9770cd531..a19c42155e751581d072f9962b3836bf258d2fe8 100644 (file)
@@ -1,31 +1,58 @@
-// $Id$\r
 /**\r
  * Copyright (C) 2007 EDIT\r
- * European Distributed Institute of Taxonomy \r
+ * European Distributed Institute of Taxonomy\r
  * http://www.e-taxonomy.eu\r
- * \r
+ *\r
  * The contents of this file are subject to the Mozilla Public License Version 1.1\r
  * See LICENSE.TXT at the top of this package for the full license terms.\r
  */\r
 \r
 package eu.etaxonomy.cdm.api.service;\r
 \r
+import java.io.IOException;\r
 import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.HashMap;\r
 import java.util.List;\r
+import java.util.Map;\r
 import java.util.Set;\r
+import java.util.UUID;\r
+import java.util.stream.Collectors;\r
 \r
+import org.apache.http.HttpException;\r
 import org.springframework.beans.factory.annotation.Autowired;\r
 import org.springframework.stereotype.Service;\r
-import org.springframework.transaction.annotation.Propagation;\r
 import org.springframework.transaction.annotation.Transactional;\r
 \r
+import eu.etaxonomy.cdm.api.service.config.DeleteConfiguratorBase;\r
+import eu.etaxonomy.cdm.api.service.config.MediaDeletionConfigurator;\r
+import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;\r
+import eu.etaxonomy.cdm.api.service.media.MediaInfoFactory;\r
 import eu.etaxonomy.cdm.api.service.pager.Pager;\r
 import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;\r
+import eu.etaxonomy.cdm.common.media.CdmImageInfo;\r
 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;\r
+import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;\r
+import eu.etaxonomy.cdm.model.common.CdmBase;\r
+import eu.etaxonomy.cdm.model.common.ICdmBase;\r
+import eu.etaxonomy.cdm.model.description.DescriptionBase;\r
+import eu.etaxonomy.cdm.model.description.IDescribable;\r
 import eu.etaxonomy.cdm.model.description.MediaKey;\r
+import eu.etaxonomy.cdm.model.description.SpecimenDescription;\r
+import eu.etaxonomy.cdm.model.description.TaxonDescription;\r
+import eu.etaxonomy.cdm.model.description.TaxonNameDescription;\r
+import eu.etaxonomy.cdm.model.description.TextData;\r
 import eu.etaxonomy.cdm.model.location.NamedArea;\r
 import eu.etaxonomy.cdm.model.media.Media;\r
+import eu.etaxonomy.cdm.model.media.MediaRepresentation;\r
+import eu.etaxonomy.cdm.model.media.MediaRepresentationPart;\r
 import eu.etaxonomy.cdm.model.media.Rights;\r
+import eu.etaxonomy.cdm.model.metadata.CdmPreference;\r
+import eu.etaxonomy.cdm.model.metadata.PreferencePredicate;\r
+import eu.etaxonomy.cdm.model.metadata.PreferenceSubject;\r
+import eu.etaxonomy.cdm.model.name.TaxonName;\r
+import eu.etaxonomy.cdm.model.occurrence.MediaSpecimen;\r
+import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;\r
 import eu.etaxonomy.cdm.model.taxon.Taxon;\r
 import eu.etaxonomy.cdm.persistence.dao.media.IMediaDao;\r
 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;\r
@@ -34,43 +61,292 @@ import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
 @Transactional(readOnly=true)\r
 public class MediaServiceImpl extends IdentifiableServiceBase<Media,IMediaDao> implements IMediaService {\r
 \r
-       @Autowired\r
+    @Override\r
+    @Autowired\r
        protected void setDao(IMediaDao dao) {\r
                this.dao = dao;\r
        }\r
 \r
-       public Pager<MediaKey> getMediaKeys(Set<Taxon> taxonomicScope, Set<NamedArea> geoScopes, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {\r
-        Integer numberOfResults = dao.countMediaKeys(taxonomicScope, geoScopes);\r
-               \r
-               List<MediaKey> results = new ArrayList<MediaKey>();\r
+       @Autowired\r
+    private IOccurrenceService specimenService;\r
+       @Autowired\r
+    private ITaxonService taxonService;\r
+       @Autowired\r
+    private INameService nameService;\r
+       @Autowired\r
+       private IPreferenceService prefsService;\r
+    @Autowired\r
+    private MediaInfoFactory mediaInfoFactory; // FIXME define and use interface\r
+\r
+\r
+       @Override\r
+    public Pager<MediaKey> getMediaKeys(Set<Taxon> taxonomicScope, Set<NamedArea> geoScopes, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {\r
+        long numberOfResults = dao.countMediaKeys(taxonomicScope, geoScopes);\r
+\r
+               List<MediaKey> results = new ArrayList<>();\r
                if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)\r
-                       results = dao.getMediaKeys(taxonomicScope, geoScopes, pageSize, pageNumber, propertyPaths); \r
+                       results = dao.getMediaKeys(taxonomicScope, geoScopes, pageSize, pageNumber, propertyPaths);\r
                }\r
-               \r
-               return new DefaultPagerImpl<MediaKey>(pageNumber, numberOfResults, pageSize, results);\r
+\r
+               return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);\r
        }\r
-       \r
-       public Pager<Rights> getRights(Media t, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {\r
-        Integer numberOfResults = dao.countRights(t);\r
-               \r
-               List<Rights> results = new ArrayList<Rights>();\r
+\r
+       @Override\r
+    public Pager<Rights> getRights(Media t, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {\r
+        long numberOfResults = dao.countRights(t);\r
+\r
+               List<Rights> results = new ArrayList<>();\r
                if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)\r
-                       results = dao.getRights(t, pageSize, pageNumber,propertyPaths); \r
+                       results = dao.getRights(t, pageSize, pageNumber,propertyPaths);\r
                }\r
-               \r
-               return new DefaultPagerImpl<Rights>(pageNumber, numberOfResults, pageSize, results);\r
+\r
+               return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);\r
        }\r
 \r
-       /* (non-Javadoc)\r
-        * @see eu.etaxonomy.cdm.api.service.IIdentifiableEntityService#updateTitleCache(java.lang.Integer, eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy)\r
-        */\r
        @Override\r
        @Transactional(readOnly = false)\r
-    public void updateTitleCache(Class<? extends Media> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<Media> cacheStrategy, IProgressMonitor monitor) {\r
-               //IIdentifiableEntityCacheStrategy<Media> cacheStrategy = MediaDefaultCacheStrategy();\r
+    public UpdateResult updateCaches(Class<? extends Media> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<Media> cacheStrategy, IProgressMonitor monitor) {\r
                if (clazz == null){\r
                        clazz = Media.class;\r
                }\r
-               super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);\r
+               return super.updateCachesImpl(clazz, stepSize, cacheStrategy, monitor);\r
        }\r
+\r
+        @Override\r
+        @Transactional(readOnly=false)\r
+        public DeleteResult delete(Set<UUID> mediaUuids, MediaDeletionConfigurator config) {\r
+            DeleteResult result = new DeleteResult();\r
+            for (UUID uuid:mediaUuids){\r
+                result.includeResult(delete(uuid, config));\r
+            }\r
+            return result;\r
+\r
+        }\r
+\r
+    @Override\r
+    @Transactional(readOnly=false)\r
+    public DeleteResult delete(UUID mediaUuid, MediaDeletionConfigurator config) {\r
+        DeleteResult result = new DeleteResult();\r
+        Media media = this.load(mediaUuid);\r
+        if (media == null){\r
+            return result;\r
+        }\r
+        result = isDeletable(mediaUuid, config);\r
+        if (result.isOk()){\r
+            Set<CdmBase> references = commonService.getReferencingObjectsForDeletion(media);\r
+            for (CdmBase ref: references){\r
+\r
+                IDescribable<?> updatedObject = null;\r
+                IService<ICdmBase> service = null;\r
+                if (ref instanceof TextData){\r
+\r
+                    TextData textData = HibernateProxyHelper.deproxy(ref, TextData.class);\r
+                    DescriptionBase<?> description = HibernateProxyHelper.deproxy(textData.getInDescription(), DescriptionBase.class);\r
+\r
+                    IDescribable<?> objectToUpdate = null;\r
+                    boolean deleteIsMatchingInstance = false;\r
+                    if (description instanceof TaxonDescription){\r
+                        objectToUpdate = ((TaxonDescription)description).getTaxon();\r
+                        deleteIsMatchingInstance = config.getDeleteFrom() instanceof Taxon;\r
+                        service = (IService)taxonService;\r
+                    }else if (description instanceof SpecimenDescription){\r
+                        objectToUpdate = ((SpecimenDescription)description).getDescribedSpecimenOrObservation();\r
+                        deleteIsMatchingInstance = config.getDeleteFrom() instanceof SpecimenOrObservationBase;\r
+                        service = (IService)specimenService;\r
+                    }else if (description instanceof TaxonNameDescription){\r
+                        objectToUpdate = ((TaxonNameDescription)description).getTaxonName();\r
+                        deleteIsMatchingInstance = config.getDeleteFrom() instanceof TaxonName;\r
+                        service = (IService)nameService;\r
+                    }else{\r
+                        throw new RuntimeException("Unsupported DescriptionBase class");\r
+                    }\r
+\r
+                    if (objectToUpdate == null ){\r
+                        continue;\r
+                    } else if ( (config.isDeleteFromDescription() && deleteIsMatchingInstance  &&\r
+                                   config.getDeleteFrom().getId() == objectToUpdate.getId())\r
+                                || config.isDeleteFromEveryWhere()){\r
+                        updatedObject = handleDeleteMedia(media, textData, description,\r
+                                (IDescribable)objectToUpdate);\r
+                    } else {\r
+                        // this should not be happen, because it is not deletable. see isDeletable\r
+                        result.setAbort();\r
+                    }\r
+\r
+//                } else if (ref instanceof MediaSpecimen && config.getDeleteFrom().getId() == ref.getId() && config.getDeleteFrom() instanceof MediaSpecimen){\r
+//                        MediaSpecimen mediaSpecimen = HibernateProxyHelper.deproxy(ref, MediaSpecimen.class);\r
+//                        mediaSpecimen.setMediaSpecimen(null);\r
+//                        updatedObject = mediaSpecimen;\r
+//                        service = (IService)specimenService;\r
+                }else if (ref instanceof MediaRepresentation){\r
+                    continue;\r
+                }else {\r
+                    result.setAbort();\r
+                }\r
+\r
+                if (updatedObject != null){\r
+                    service.update(updatedObject); //service should always be != null if updatedObject != null\r
+                    result.addUpdatedObject((CdmBase)updatedObject);\r
+                }\r
+            }\r
+            if (result.isOk()){\r
+                dao.delete(media);\r
+                result.addDeletedObject(media);\r
+            }\r
+\r
+        }\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * @param media\r
+     * @param textData\r
+     * @param desc\r
+     * @param taxon\r
+     */\r
+    private IDescribable<DescriptionBase<?>> handleDeleteMedia(Media media, TextData textData,\r
+            DescriptionBase<?> desc, IDescribable<DescriptionBase<?>> describable) {\r
+        while(textData.getMedia().contains(media)){\r
+            textData.removeMedia(media);\r
+        }\r
+\r
+        return describable;\r
+    }\r
+\r
+\r
+    @Override\r
+    public DeleteResult isDeletable(UUID mediaUuid, DeleteConfiguratorBase config){\r
+        DeleteResult result = new DeleteResult();\r
+        Media media = this.load(mediaUuid);\r
+        Set<CdmBase> references = commonService.getReferencingObjectsForDeletion(media);\r
+        MediaDeletionConfigurator mediaConfig = (MediaDeletionConfigurator)config;\r
+        CdmBase deleteFrom = mediaConfig.getDeleteFrom();\r
+\r
+        if (mediaConfig.isDeleteFromEveryWhere()){\r
+           return result;\r
+        }\r
+        for (CdmBase ref: references){\r
+            String message = null;\r
+            if (ref instanceof MediaRepresentation){\r
+                continue;\r
+            }else if (ref instanceof TextData){\r
+                TextData textData = HibernateProxyHelper.deproxy(ref, TextData.class);\r
+                DescriptionBase<?> description = HibernateProxyHelper.deproxy(textData.getInDescription(), DescriptionBase.class);\r
+\r
+                if (description instanceof TaxonDescription){\r
+                    TaxonDescription desc = HibernateProxyHelper.deproxy(description, TaxonDescription.class);\r
+                    if (desc.getTaxon() == null || (mediaConfig.isDeleteFromDescription() && (deleteFrom instanceof Taxon && ((Taxon)deleteFrom).getId() == desc.getTaxon().getId()))){\r
+                        continue;\r
+                    } else{\r
+                        message = "The media can't be deleted from the database because it is referenced by a taxon. ("+desc.getTaxon().getTitleCache()+")";\r
+                        result.setAbort();\r
+                    }\r
+\r
+                } else if (description instanceof SpecimenDescription){\r
+                    SpecimenDescription desc = HibernateProxyHelper.deproxy(description, SpecimenDescription.class);\r
+                    if (desc.getDescribedSpecimenOrObservation() == null || (mediaConfig.isDeleteFromDescription() && (deleteFrom instanceof SpecimenOrObservationBase && ((SpecimenOrObservationBase)deleteFrom).getId() == desc.getDescribedSpecimenOrObservation().getId()))){\r
+                        continue;\r
+                    } else{\r
+                        message = "The media can't be deleted from the database because it is referenced by a specimen or observation. ("+desc.getDescribedSpecimenOrObservation().getTitleCache()+")";\r
+                        result.setAbort();\r
+                    }\r
+                } else if (description instanceof TaxonNameDescription){\r
+                    TaxonNameDescription desc = HibernateProxyHelper.deproxy(description, TaxonNameDescription.class);\r
+                    if (desc.getTaxonName() == null || (mediaConfig.isDeleteFromDescription() && (deleteFrom instanceof TaxonName && ((TaxonName)deleteFrom).getId() == desc.getTaxonName().getId()))){\r
+                        continue;\r
+                    } else{\r
+                        message = "The media can't be deleted from the database because it is referenced by a scientific name. ("+desc.getTaxonName().getTitleCache()+")";\r
+                        result.setAbort();\r
+                    }\r
+                }\r
+            }else if (ref instanceof MediaSpecimen){\r
+               message = "The media can't be deleted from the database because it is referenced by a mediaspecimen. ("+((MediaSpecimen)ref).getTitleCache()+")";\r
+               result.setAbort();\r
+            }else {\r
+                message = "The media can't be completely deleted because it is referenced by another " + ref.getUserFriendlyTypeName() ;\r
+                result.setAbort();\r
+            }\r
+            if (message != null){\r
+                result.addException(new ReferencedObjectUndeletableException(message));\r
+                result.addRelatedObject(ref);\r
+            }\r
+        }\r
+\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * Reads the metadata as stored in the file or web resource and filters the data by the include and exclude lists of key names\r
+     * as stored in the data base properties {@link PreferencePredicate#MediaMetadataKeynameExcludes} and {@link PreferencePredicate#MediaMetadataKeynameExcludes}\r
+     * <p>\r
+     * Metadata of multiple parts is merged into one common metadata map whereas the later part being read may overwrite data from previous parts.\r
+     * The consequences of this can be neglected since we don't expect that multiple parts are actually being used.\r
+     *\r
+     * @param representation\r
+     * @return\r
+     * @throws IOException\r
+     * @throws HttpException\r
+     */\r
+    @Override\r
+    public Map<String, String> readResourceMetadataFiltered(MediaRepresentation representation) throws IOException, HttpException {\r
+\r
+        List<String> includes = mediaMetadataKeyIncludes();\r
+        List<String> excludes = mediaMetadataKeyExludes();\r
+        Map<String, String> metadata = new HashMap<>();\r
+\r
+        for(MediaRepresentationPart part : representation.getParts()) {\r
+            CdmImageInfo iInfo =  mediaInfoFactory.cdmImageInfoWithMetaData(part.getUri());\r
+            if(iInfo.getMetaData() != null) {\r
+                metadata.putAll(iInfo.getMetaData());\r
+            }\r
+        }\r
+\r
+        if(logger.isDebugEnabled()) {\r
+            logger.debug("meta data as read from all parts: " + metadata.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining(", ", "{", "}")));\r
+        }\r
+\r
+        if(!includes.isEmpty()) {\r
+            metadata = metadata.entrySet()\r
+                    .stream()\r
+                    .filter( e -> containsCaseInsensitive(e.getKey(), includes))\r
+                    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\r
+            if(logger.isDebugEnabled()) {\r
+                logger.debug("meta filtered by includes: " + metadata.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining(", ", "{", "}")));\r
+            }\r
+        }\r
+        if(!excludes.isEmpty()) {\r
+            metadata = metadata.entrySet()\r
+                    .stream()\r
+                    .filter( e -> !containsCaseInsensitive(e.getKey(), excludes))\r
+                    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\r
+            if(logger.isDebugEnabled()) {\r
+                logger.debug("meta filtered by excludes: " + metadata.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining(", ", "{", "}")));\r
+            }\r
+        }\r
+\r
+        if(metadata == null) {\r
+            metadata = new HashMap<>();\r
+        }\r
+        return metadata;\r
+    }\r
+\r
+    private boolean containsCaseInsensitive(String s, List<String> l){\r
+        return l.stream().anyMatch(x -> x.equalsIgnoreCase(s));\r
+    }\r
+\r
+    protected List<String> mediaMetadataKeyExludes(){\r
+        CdmPreference pref = prefsService.findExact(CdmPreference.NewKey(PreferenceSubject.NewDatabaseInstance(), PreferencePredicate.MediaMetadataKeynameExcludes));\r
+        if(pref == null) {\r
+            return new ArrayList<>();\r
+        }\r
+        return pref.splitStringListValue();\r
+    }\r
+\r
+    protected List<String> mediaMetadataKeyIncludes(){\r
+        CdmPreference pref = prefsService.findExact(CdmPreference.NewKey(PreferenceSubject.NewDatabaseInstance(), PreferencePredicate.MediaMetadataKeynameIncludes));\r
+        if(pref == null) {\r
+            return Arrays.asList(PreferencePredicate.MediaMetadataKeynameIncludes.getDefaultValue().toString().split(","));\r
+        }\r
+        return pref.splitStringListValue();\r
+    }\r
 }\r