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
.cdm
.api
.service
;
11 import java
.io
.IOException
;
12 import java
.util
.ArrayList
;
13 import java
.util
.Arrays
;
14 import java
.util
.HashMap
;
15 import java
.util
.List
;
18 import java
.util
.UUID
;
19 import java
.util
.stream
.Collectors
;
21 import org
.apache
.http
.HttpException
;
22 import org
.apache
.logging
.log4j
.LogManager
;
23 import org
.apache
.logging
.log4j
.Logger
;
24 import org
.springframework
.beans
.factory
.annotation
.Autowired
;
25 import org
.springframework
.stereotype
.Service
;
26 import org
.springframework
.transaction
.annotation
.Transactional
;
28 import eu
.etaxonomy
.cdm
.api
.service
.config
.DeleteConfiguratorBase
;
29 import eu
.etaxonomy
.cdm
.api
.service
.config
.MediaDeletionConfigurator
;
30 import eu
.etaxonomy
.cdm
.api
.service
.exception
.ReferencedObjectUndeletableException
;
31 import eu
.etaxonomy
.cdm
.api
.service
.media
.MediaInfoFactory
;
32 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
33 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.DefaultPagerImpl
;
34 import eu
.etaxonomy
.cdm
.common
.media
.CdmImageInfo
;
35 import eu
.etaxonomy
.cdm
.common
.monitor
.IProgressMonitor
;
36 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
37 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
38 import eu
.etaxonomy
.cdm
.model
.common
.ICdmBase
;
39 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
40 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionBase
;
41 import eu
.etaxonomy
.cdm
.model
.description
.IDescribable
;
42 import eu
.etaxonomy
.cdm
.model
.description
.MediaKey
;
43 import eu
.etaxonomy
.cdm
.model
.description
.SpecimenDescription
;
44 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
45 import eu
.etaxonomy
.cdm
.model
.description
.TaxonNameDescription
;
46 import eu
.etaxonomy
.cdm
.model
.description
.TextData
;
47 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
48 import eu
.etaxonomy
.cdm
.model
.media
.Media
;
49 import eu
.etaxonomy
.cdm
.model
.media
.MediaRepresentation
;
50 import eu
.etaxonomy
.cdm
.model
.media
.MediaRepresentationPart
;
51 import eu
.etaxonomy
.cdm
.model
.media
.Rights
;
52 import eu
.etaxonomy
.cdm
.model
.metadata
.CdmPreference
;
53 import eu
.etaxonomy
.cdm
.model
.metadata
.PreferencePredicate
;
54 import eu
.etaxonomy
.cdm
.model
.metadata
.PreferenceSubject
;
55 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
56 import eu
.etaxonomy
.cdm
.model
.occurrence
.MediaSpecimen
;
57 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
58 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
59 import eu
.etaxonomy
.cdm
.persistence
.dao
.media
.IMediaDao
;
60 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
63 @Transactional(readOnly
=true)
64 public class MediaServiceImpl
extends IdentifiableServiceBase
<Media
,IMediaDao
> implements IMediaService
{
66 private static final Logger logger
= LogManager
.getLogger();
70 protected void setDao(IMediaDao dao
) {
75 private IOccurrenceService specimenService
;
77 private ITaxonService taxonService
;
79 private INameService nameService
;
81 private IPreferenceService prefsService
;
83 private MediaInfoFactory mediaInfoFactory
; // FIXME define and use interface
87 public Pager
<MediaKey
> getMediaKeys(Set
<Taxon
> taxonomicScope
, Set
<NamedArea
> geoScopes
, Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
88 long numberOfResults
= dao
.countMediaKeys(taxonomicScope
, geoScopes
);
90 List
<MediaKey
> results
= new ArrayList
<>();
91 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
92 results
= dao
.getMediaKeys(taxonomicScope
, geoScopes
, pageSize
, pageNumber
, propertyPaths
);
95 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
99 public Pager
<Rights
> getRights(Media t
, Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
100 long numberOfResults
= dao
.countRights(t
);
102 List
<Rights
> results
= new ArrayList
<>();
103 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
104 results
= dao
.getRights(t
, pageSize
, pageNumber
,propertyPaths
);
107 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
111 @Transactional(readOnly
= false)
112 public UpdateResult
updateCaches(Class
<?
extends Media
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<Media
> cacheStrategy
, IProgressMonitor monitor
) {
116 return super.updateCachesImpl(clazz
, stepSize
, cacheStrategy
, monitor
);
120 @Transactional(readOnly
=false)
121 public DeleteResult
delete(Set
<UUID
> mediaUuids
, MediaDeletionConfigurator config
) {
122 DeleteResult result
= new DeleteResult();
123 for (UUID uuid
:mediaUuids
){
124 result
.includeResult(delete(uuid
, config
));
131 @Transactional(readOnly
=false)
132 public DeleteResult
delete(UUID mediaUuid
, MediaDeletionConfigurator config
) {
133 DeleteResult result
= new DeleteResult();
134 Media media
= this.load(mediaUuid
);
138 result
= isDeletable(mediaUuid
, config
);
140 Set
<CdmBase
> references
= commonService
.getReferencingObjectsForDeletion(media
);
141 for (CdmBase ref
: references
){
143 IDescribable
<?
> updatedObject
= null;
144 IService
<ICdmBase
> service
= null;
145 if (ref
instanceof TextData
){
147 TextData textData
= HibernateProxyHelper
.deproxy(ref
, TextData
.class);
148 DescriptionBase
<?
> description
= HibernateProxyHelper
.deproxy(textData
.getInDescription(), DescriptionBase
.class);
150 IDescribable
<?
> objectToUpdate
= null;
151 boolean deleteIsMatchingInstance
= false;
152 if (description
instanceof TaxonDescription
){
153 objectToUpdate
= ((TaxonDescription
)description
).getTaxon();
154 deleteIsMatchingInstance
= config
.getDeleteFrom() instanceof Taxon
;
155 service
= (IService
)taxonService
;
156 }else if (description
instanceof SpecimenDescription
){
157 objectToUpdate
= ((SpecimenDescription
)description
).getDescribedSpecimenOrObservation();
158 deleteIsMatchingInstance
= config
.getDeleteFrom() instanceof SpecimenOrObservationBase
;
159 service
= (IService
)specimenService
;
160 }else if (description
instanceof TaxonNameDescription
){
161 objectToUpdate
= ((TaxonNameDescription
)description
).getTaxonName();
162 deleteIsMatchingInstance
= config
.getDeleteFrom() instanceof TaxonName
;
163 service
= (IService
)nameService
;
165 throw new RuntimeException("Unsupported DescriptionBase class");
168 if (objectToUpdate
== null ){
170 } else if ( (config
.isDeleteFromDescription() && deleteIsMatchingInstance
&&
171 config
.getDeleteFrom().getId() == objectToUpdate
.getId())
172 || config
.isDeleteFromEveryWhere()){
173 updatedObject
= handleDeleteMedia(media
, textData
, description
,
174 (IDescribable
)objectToUpdate
);
177 // this should not be happen, because it is not deletable. see isDeletable
181 // } else if (ref instanceof MediaSpecimen && config.getDeleteFrom().getId() == ref.getId() && config.getDeleteFrom() instanceof MediaSpecimen){
182 // MediaSpecimen mediaSpecimen = HibernateProxyHelper.deproxy(ref, MediaSpecimen.class);
183 // mediaSpecimen.setMediaSpecimen(null);
184 // updatedObject = mediaSpecimen;
185 // service = (IService)specimenService;
186 }else if (ref
instanceof MediaRepresentation
){
193 if (updatedObject
!= null){
194 //service.update(updatedObject); //service should always be != null if updatedObject != null
195 result
.addUpdatedObject((CdmBase
)updatedObject
);
200 result
.addDeletedObject(media
);
213 private IDescribable
<DescriptionBase
<?
>> handleDeleteMedia(Media media
, TextData textData
,
214 DescriptionBase
<?
> desc
, IDescribable
<DescriptionBase
<?
>> describable
) {
215 while(textData
.getMedia().contains(media
)){
216 textData
.removeMedia(media
);
224 public DeleteResult
isDeletable(UUID mediaUuid
, DeleteConfiguratorBase config
){
225 DeleteResult result
= new DeleteResult();
226 Media media
= this.load(mediaUuid
);
227 Set
<CdmBase
> references
= commonService
.getReferencingObjectsForDeletion(media
);
228 MediaDeletionConfigurator mediaConfig
= (MediaDeletionConfigurator
)config
;
229 CdmBase deleteFrom
= mediaConfig
.getDeleteFrom();
231 // if (mediaConfig.isDeleteFromEveryWhere()){
234 for (CdmBase ref
: references
){
235 String message
= null;
236 if (ref
instanceof MediaRepresentation
){
238 }else if (ref
instanceof TextData
){
239 TextData textData
= HibernateProxyHelper
.deproxy(ref
, TextData
.class);
240 DescriptionBase
<?
> description
= HibernateProxyHelper
.deproxy(textData
.getInDescription(), DescriptionBase
.class);
242 if (description
instanceof TaxonDescription
){
243 TaxonDescription desc
= HibernateProxyHelper
.deproxy(description
, TaxonDescription
.class);
244 if (mediaConfig
.isDeleteFromEveryWhere() ||(desc
.getTaxon() == null || (mediaConfig
.isDeleteFromDescription() && (deleteFrom
instanceof Taxon
&& ((Taxon
)deleteFrom
).getId() == desc
.getTaxon().getId())))){
247 message
= "The media can't be deleted from the database because it is referenced by a taxon. ("+desc
.getTaxon().getTitleCache()+")";
251 } else if (description
instanceof SpecimenDescription
){
252 SpecimenDescription desc
= HibernateProxyHelper
.deproxy(description
, SpecimenDescription
.class);
253 if (mediaConfig
.isDeleteFromEveryWhere() || (desc
.getDescribedSpecimenOrObservation() == null || (mediaConfig
.isDeleteFromDescription() && (deleteFrom
instanceof SpecimenOrObservationBase
&& ((SpecimenOrObservationBase
)deleteFrom
).getId() == desc
.getDescribedSpecimenOrObservation().getId())))){
256 message
= "The media can't be deleted from the database because it is referenced by a specimen or observation. ("+desc
.getDescribedSpecimenOrObservation().getTitleCache()+")";
259 } else if (description
instanceof TaxonNameDescription
){
260 TaxonNameDescription desc
= HibernateProxyHelper
.deproxy(description
, TaxonNameDescription
.class);
261 if (mediaConfig
.isDeleteFromEveryWhere() || (desc
.getTaxonName() == null || (mediaConfig
.isDeleteFromDescription() && (deleteFrom
instanceof TaxonName
&& ((TaxonName
)deleteFrom
).getId() == desc
.getTaxonName().getId())))){
264 message
= "The media can't be deleted from the database because it is referenced by a scientific name. ("+desc
.getTaxonName().getTitleCache()+")";
268 }else if (ref
instanceof MediaSpecimen
){
269 message
= "The media can't be deleted from the database because it is referenced by a mediaspecimen. ("+((MediaSpecimen
)ref
).getTitleCache()+")";
272 message
= "The media can't be completely deleted because it is referenced by a " + ref
.getUserFriendlyTypeName();
273 if (ref
instanceof IdentifiableEntity
) {
274 message
+= ": " + ((IdentifiableEntity
)ref
).getTitleCache();
278 if (message
!= null){
279 result
.addException(new ReferencedObjectUndeletableException(message
));
280 result
.addRelatedObject(ref
);
288 * Reads the metadata as stored in the file or web resource and filters the data by the include and exclude lists of key names
289 * as stored in the data base properties {@link PreferencePredicate#MediaMetadataKeynameExcludes} and {@link PreferencePredicate#MediaMetadataKeynameExcludes}
291 * Metadata of multiple parts is merged into one common metadata map whereas the later part being read may overwrite data from previous parts.
292 * The consequences of this can be neglected since we don't expect that multiple parts are actually being used.
294 * @param representation
296 * @throws IOException
297 * @throws HttpException
300 public Map
<String
, String
> readResourceMetadataFiltered(MediaRepresentation representation
) throws IOException
, HttpException
{
302 List
<String
> includes
= mediaMetadataKeyIncludes();
303 List
<String
> excludes
= mediaMetadataKeyExludes();
304 Map
<String
, String
> metadata
= new HashMap
<>();
306 for(MediaRepresentationPart part
: representation
.getParts()) {
307 CdmImageInfo iInfo
= mediaInfoFactory
.cdmImageInfo(part
.getUri(), true);
308 if(iInfo
.getMetaData() != null) {
309 metadata
.putAll(iInfo
.getMetaData());
312 if(logger
.isDebugEnabled()) {
313 logger
.debug("meta data as read from all parts: " + metadata
.entrySet().stream().map(e
-> e
.getKey() + "=" + e
.getValue()).collect(Collectors
.joining(", ", "{", "}")));
316 if(!includes
.isEmpty()) {
317 metadata
= metadata
.entrySet()
319 .filter( e
-> containsCaseInsensitive(e
.getKey(), includes
))
320 .collect(Collectors
.toMap(Map
.Entry
::getKey
, Map
.Entry
::getValue
));
321 if(logger
.isDebugEnabled()) {
322 logger
.debug("meta filtered by includes: " + metadata
.entrySet().stream().map(e
-> e
.getKey() + "=" + e
.getValue()).collect(Collectors
.joining(", ", "{", "}")));
325 if(!excludes
.isEmpty()) {
326 metadata
= metadata
.entrySet()
328 .filter( e
-> !containsCaseInsensitive(e
.getKey(), excludes
))
329 .collect(Collectors
.toMap(Map
.Entry
::getKey
, Map
.Entry
::getValue
));
330 if(logger
.isDebugEnabled()) {
331 logger
.debug("meta filtered by excludes: " + metadata
.entrySet().stream().map(e
-> e
.getKey() + "=" + e
.getValue()).collect(Collectors
.joining(", ", "{", "}")));
335 if(metadata
== null) {
336 metadata
= new HashMap
<>();
341 private boolean containsCaseInsensitive(String s
, List
<String
> l
){
342 return l
.stream().anyMatch(x
-> x
.equalsIgnoreCase(s
));
345 protected List
<String
> mediaMetadataKeyExludes(){
346 CdmPreference pref
= prefsService
.findExact(CdmPreference
.NewKey(PreferenceSubject
.NewDatabaseInstance(), PreferencePredicate
.MediaMetadataKeynameExcludes
));
348 return new ArrayList
<>();
350 return pref
.splitStringListValue();
353 protected List
<String
> mediaMetadataKeyIncludes(){
354 CdmPreference pref
= prefsService
.findExact(CdmPreference
.NewKey(PreferenceSubject
.NewDatabaseInstance(), PreferencePredicate
.MediaMetadataKeynameIncludes
));
356 return Arrays
.asList(PreferencePredicate
.MediaMetadataKeynameIncludes
.getDefaultValue().toString().split(","));
358 return pref
.splitStringListValue();