cleanup
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / MediaServiceImpl.java
1 /**
2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
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.
8 */
9 package eu.etaxonomy.cdm.api.service;
10
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;
16 import java.util.Map;
17 import java.util.Set;
18 import java.util.UUID;
19 import java.util.stream.Collectors;
20
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;
27
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;
61
62 @Service
63 @Transactional(readOnly=true)
64 public class MediaServiceImpl extends IdentifiableServiceBase<Media,IMediaDao> implements IMediaService {
65
66 private static final Logger logger = LogManager.getLogger();
67
68 @Override
69 @Autowired
70 protected void setDao(IMediaDao dao) {
71 this.dao = dao;
72 }
73
74 @Autowired
75 private IOccurrenceService specimenService;
76 @Autowired
77 private ITaxonService taxonService;
78 @Autowired
79 private INameService nameService;
80 @Autowired
81 private IPreferenceService prefsService;
82 @Autowired
83 private MediaInfoFactory mediaInfoFactory; // FIXME define and use interface
84
85
86 @Override
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);
89
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);
93 }
94
95 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
96 }
97
98 @Override
99 public Pager<Rights> getRights(Media t, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
100 long numberOfResults = dao.countRights(t);
101
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);
105 }
106
107 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
108 }
109
110 @Override
111 @Transactional(readOnly = false)
112 public UpdateResult updateCaches(Class<? extends Media> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<Media> cacheStrategy, IProgressMonitor monitor) {
113 if (clazz == null){
114 clazz = Media.class;
115 }
116 return super.updateCachesImpl(clazz, stepSize, cacheStrategy, monitor);
117 }
118
119 @Override
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));
125 }
126 return result;
127
128 }
129
130 @Override
131 @Transactional(readOnly=false)
132 public DeleteResult delete(UUID mediaUuid, MediaDeletionConfigurator config) {
133 DeleteResult result = new DeleteResult();
134 Media media = this.load(mediaUuid);
135 if (media == null){
136 return result;
137 }
138 result = isDeletable(mediaUuid, config);
139 if (result.isOk()){
140 Set<CdmBase> references = commonService.getReferencingObjectsForDeletion(media);
141 for (CdmBase ref: references){
142
143 IDescribable<?> updatedObject = null;
144 IService<ICdmBase> service = null;
145 if (ref instanceof TextData){
146
147 TextData textData = HibernateProxyHelper.deproxy(ref, TextData.class);
148 DescriptionBase<?> description = HibernateProxyHelper.deproxy(textData.getInDescription(), DescriptionBase.class);
149
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;
164 }else{
165 throw new RuntimeException("Unsupported DescriptionBase class");
166 }
167
168 if (objectToUpdate == null ){
169 continue;
170 } else if ( (config.isDeleteFromDescription() && deleteIsMatchingInstance &&
171 config.getDeleteFrom().getId() == objectToUpdate.getId())
172 || config.isDeleteFromEveryWhere()){
173 updatedObject = handleDeleteMedia(media, textData, description,
174 (IDescribable)objectToUpdate);
175 } else {
176
177 // this should not be happen, because it is not deletable. see isDeletable
178 result.setAbort();
179 }
180
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){
187 continue;
188 }else {
189
190 result.setAbort();
191 }
192
193 if (updatedObject != null){
194 //service.update(updatedObject); //service should always be != null if updatedObject != null
195 result.addUpdatedObject((CdmBase)updatedObject);
196 }
197 }
198 if (result.isOk()){
199 dao.delete(media);
200 result.addDeletedObject(media);
201 }
202
203 }
204 return result;
205 }
206
207 /**
208 * @param media
209 * @param textData
210 * @param desc
211 * @param taxon
212 */
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);
217 }
218
219 return describable;
220 }
221
222
223 @Override
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();
230
231 // if (mediaConfig.isDeleteFromEveryWhere()){
232 // return result;
233 // }
234 for (CdmBase ref: references){
235 String message = null;
236 if (ref instanceof MediaRepresentation){
237 continue;
238 }else if (ref instanceof TextData){
239 TextData textData = HibernateProxyHelper.deproxy(ref, TextData.class);
240 DescriptionBase<?> description = HibernateProxyHelper.deproxy(textData.getInDescription(), DescriptionBase.class);
241
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())))){
245 continue;
246 } else{
247 message = "The media can't be deleted from the database because it is referenced by a taxon. ("+desc.getTaxon().getTitleCache()+")";
248 result.setAbort();
249 }
250
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())))){
254 continue;
255 } else{
256 message = "The media can't be deleted from the database because it is referenced by a specimen or observation. ("+desc.getDescribedSpecimenOrObservation().getTitleCache()+")";
257 result.setAbort();
258 }
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())))){
262 continue;
263 } else{
264 message = "The media can't be deleted from the database because it is referenced by a scientific name. ("+desc.getTaxonName().getTitleCache()+")";
265 result.setAbort();
266 }
267 }
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()+")";
270 result.setAbort();
271 }else {
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();
275 }
276 result.setAbort();
277 }
278 if (message != null){
279 result.addException(new ReferencedObjectUndeletableException(message));
280 result.addRelatedObject(ref);
281 }
282 }
283
284 return result;
285 }
286
287 /**
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}
290 * <p>
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.
293 *
294 * @param representation
295 * @return
296 * @throws IOException
297 * @throws HttpException
298 */
299 @Override
300 public Map<String, String> readResourceMetadataFiltered(MediaRepresentation representation) throws IOException, HttpException {
301
302 List<String> includes = mediaMetadataKeyIncludes();
303 List<String> excludes = mediaMetadataKeyExludes();
304 Map<String, String> metadata = new HashMap<>();
305
306 for(MediaRepresentationPart part : representation.getParts()) {
307 CdmImageInfo iInfo = mediaInfoFactory.cdmImageInfo(part.getUri(), true);
308 if(iInfo.getMetaData() != null) {
309 metadata.putAll(iInfo.getMetaData());
310 }
311 }
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(", ", "{", "}")));
314 }
315
316 if(!includes.isEmpty()) {
317 metadata = metadata.entrySet()
318 .stream()
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(", ", "{", "}")));
323 }
324 }
325 if(!excludes.isEmpty()) {
326 metadata = metadata.entrySet()
327 .stream()
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(", ", "{", "}")));
332 }
333 }
334
335 if(metadata == null) {
336 metadata = new HashMap<>();
337 }
338 return metadata;
339 }
340
341 private boolean containsCaseInsensitive(String s, List<String> l){
342 return l.stream().anyMatch(x -> x.equalsIgnoreCase(s));
343 }
344
345 protected List<String> mediaMetadataKeyExludes(){
346 CdmPreference pref = prefsService.findExact(CdmPreference.NewKey(PreferenceSubject.NewDatabaseInstance(), PreferencePredicate.MediaMetadataKeynameExcludes));
347 if(pref == null) {
348 return new ArrayList<>();
349 }
350 return pref.splitStringListValue();
351 }
352
353 protected List<String> mediaMetadataKeyIncludes(){
354 CdmPreference pref = prefsService.findExact(CdmPreference.NewKey(PreferenceSubject.NewDatabaseInstance(), PreferencePredicate.MediaMetadataKeynameIncludes));
355 if(pref == null) {
356 return Arrays.asList(PreferencePredicate.MediaMetadataKeynameIncludes.getDefaultValue().toString().split(","));
357 }
358 return pref.splitStringListValue();
359 }
360 }