-// $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
@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