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
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
10 package eu.etaxonomy.cdm.api.service;
11
12 import java.io.IOException;
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.HashMap;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Set;
19 import java.util.UUID;
20 import java.util.stream.Collectors;
21
22 import org.apache.http.HttpException;
23 import org.springframework.beans.factory.annotation.Autowired;
24 import org.springframework.stereotype.Service;
25 import org.springframework.transaction.annotation.Transactional;
26
27 import eu.etaxonomy.cdm.api.service.config.DeleteConfiguratorBase;
28 import eu.etaxonomy.cdm.api.service.config.MediaDeletionConfigurator;
29 import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
30 import eu.etaxonomy.cdm.api.service.media.MediaInfoFactory;
31 import eu.etaxonomy.cdm.api.service.pager.Pager;
32 import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
33 import eu.etaxonomy.cdm.common.media.CdmImageInfo;
34 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
35 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
36 import eu.etaxonomy.cdm.model.common.CdmBase;
37 import eu.etaxonomy.cdm.model.common.ICdmBase;
38 import eu.etaxonomy.cdm.model.description.DescriptionBase;
39 import eu.etaxonomy.cdm.model.description.IDescribable;
40 import eu.etaxonomy.cdm.model.description.MediaKey;
41 import eu.etaxonomy.cdm.model.description.SpecimenDescription;
42 import eu.etaxonomy.cdm.model.description.TaxonDescription;
43 import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
44 import eu.etaxonomy.cdm.model.description.TextData;
45 import eu.etaxonomy.cdm.model.location.NamedArea;
46 import eu.etaxonomy.cdm.model.media.Media;
47 import eu.etaxonomy.cdm.model.media.MediaRepresentation;
48 import eu.etaxonomy.cdm.model.media.MediaRepresentationPart;
49 import eu.etaxonomy.cdm.model.media.Rights;
50 import eu.etaxonomy.cdm.model.metadata.CdmPreference;
51 import eu.etaxonomy.cdm.model.metadata.PreferencePredicate;
52 import eu.etaxonomy.cdm.model.metadata.PreferenceSubject;
53 import eu.etaxonomy.cdm.model.name.TaxonName;
54 import eu.etaxonomy.cdm.model.occurrence.MediaSpecimen;
55 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
56 import eu.etaxonomy.cdm.model.taxon.Taxon;
57 import eu.etaxonomy.cdm.persistence.dao.media.IMediaDao;
58 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
59
60 @Service
61 @Transactional(readOnly=true)
62 public class MediaServiceImpl extends IdentifiableServiceBase<Media,IMediaDao> implements IMediaService {
63
64 @Override
65 @Autowired
66 protected void setDao(IMediaDao dao) {
67 this.dao = dao;
68 }
69
70 @Autowired
71 private IOccurrenceService specimenService;
72 @Autowired
73 private ITaxonService taxonService;
74 @Autowired
75 private INameService nameService;
76 @Autowired
77 private IPreferenceService prefsService;
78 @Autowired
79 private MediaInfoFactory mediaInfoFactory; // FIXME define and use interface
80
81
82 @Override
83 public Pager<MediaKey> getMediaKeys(Set<Taxon> taxonomicScope, Set<NamedArea> geoScopes, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
84 long numberOfResults = dao.countMediaKeys(taxonomicScope, geoScopes);
85
86 List<MediaKey> results = new ArrayList<>();
87 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
88 results = dao.getMediaKeys(taxonomicScope, geoScopes, pageSize, pageNumber, propertyPaths);
89 }
90
91 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
92 }
93
94 @Override
95 public Pager<Rights> getRights(Media t, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
96 long numberOfResults = dao.countRights(t);
97
98 List<Rights> results = new ArrayList<>();
99 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
100 results = dao.getRights(t, pageSize, pageNumber,propertyPaths);
101 }
102
103 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
104 }
105
106 @Override
107 @Transactional(readOnly = false)
108 public UpdateResult updateCaches(Class<? extends Media> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<Media> cacheStrategy, IProgressMonitor monitor) {
109 if (clazz == null){
110 clazz = Media.class;
111 }
112 return super.updateCachesImpl(clazz, stepSize, cacheStrategy, monitor);
113 }
114
115 @Override
116 @Transactional(readOnly=false)
117 public DeleteResult delete(Set<UUID> mediaUuids, MediaDeletionConfigurator config) {
118 DeleteResult result = new DeleteResult();
119 for (UUID uuid:mediaUuids){
120 result.includeResult(delete(uuid, config));
121 }
122 return result;
123
124 }
125
126 @Override
127 @Transactional(readOnly=false)
128 public DeleteResult delete(UUID mediaUuid, MediaDeletionConfigurator config) {
129 DeleteResult result = new DeleteResult();
130 Media media = this.load(mediaUuid);
131 if (media == null){
132 return result;
133 }
134 result = isDeletable(mediaUuid, config);
135 if (result.isOk()){
136 Set<CdmBase> references = commonService.getReferencingObjectsForDeletion(media);
137 for (CdmBase ref: references){
138
139 IDescribable<?> updatedObject = null;
140 IService<ICdmBase> service = null;
141 if (ref instanceof TextData){
142
143 TextData textData = HibernateProxyHelper.deproxy(ref, TextData.class);
144 DescriptionBase<?> description = HibernateProxyHelper.deproxy(textData.getInDescription(), DescriptionBase.class);
145
146 IDescribable<?> objectToUpdate = null;
147 boolean deleteIsMatchingInstance = false;
148 if (description instanceof TaxonDescription){
149 objectToUpdate = ((TaxonDescription)description).getTaxon();
150 deleteIsMatchingInstance = config.getDeleteFrom() instanceof Taxon;
151 service = (IService)taxonService;
152 }else if (description instanceof SpecimenDescription){
153 objectToUpdate = ((SpecimenDescription)description).getDescribedSpecimenOrObservation();
154 deleteIsMatchingInstance = config.getDeleteFrom() instanceof SpecimenOrObservationBase;
155 service = (IService)specimenService;
156 }else if (description instanceof TaxonNameDescription){
157 objectToUpdate = ((TaxonNameDescription)description).getTaxonName();
158 deleteIsMatchingInstance = config.getDeleteFrom() instanceof TaxonName;
159 service = (IService)nameService;
160 }else{
161 throw new RuntimeException("Unsupported DescriptionBase class");
162 }
163
164 if (objectToUpdate == null ){
165 continue;
166 } else if ( (config.isDeleteFromDescription() && deleteIsMatchingInstance &&
167 config.getDeleteFrom().getId() == objectToUpdate.getId())
168 || config.isDeleteFromEveryWhere()){
169 updatedObject = handleDeleteMedia(media, textData, description,
170 (IDescribable)objectToUpdate);
171 } else {
172 // this should not be happen, because it is not deletable. see isDeletable
173 result.setAbort();
174 }
175
176 // } else if (ref instanceof MediaSpecimen && config.getDeleteFrom().getId() == ref.getId() && config.getDeleteFrom() instanceof MediaSpecimen){
177 // MediaSpecimen mediaSpecimen = HibernateProxyHelper.deproxy(ref, MediaSpecimen.class);
178 // mediaSpecimen.setMediaSpecimen(null);
179 // updatedObject = mediaSpecimen;
180 // service = (IService)specimenService;
181 }else if (ref instanceof MediaRepresentation){
182 continue;
183 }else {
184 result.setAbort();
185 }
186
187 if (updatedObject != null){
188 service.update(updatedObject); //service should always be != null if updatedObject != null
189 result.addUpdatedObject((CdmBase)updatedObject);
190 }
191 }
192 if (result.isOk()){
193 dao.delete(media);
194 result.addDeletedObject(media);
195 }
196
197 }
198 return result;
199 }
200
201 /**
202 * @param media
203 * @param textData
204 * @param desc
205 * @param taxon
206 */
207 private IDescribable<DescriptionBase<?>> handleDeleteMedia(Media media, TextData textData,
208 DescriptionBase<?> desc, IDescribable<DescriptionBase<?>> describable) {
209 while(textData.getMedia().contains(media)){
210 textData.removeMedia(media);
211 }
212
213 return describable;
214 }
215
216
217 @Override
218 public DeleteResult isDeletable(UUID mediaUuid, DeleteConfiguratorBase config){
219 DeleteResult result = new DeleteResult();
220 Media media = this.load(mediaUuid);
221 Set<CdmBase> references = commonService.getReferencingObjectsForDeletion(media);
222 MediaDeletionConfigurator mediaConfig = (MediaDeletionConfigurator)config;
223 CdmBase deleteFrom = mediaConfig.getDeleteFrom();
224
225 if (mediaConfig.isDeleteFromEveryWhere()){
226 return result;
227 }
228 for (CdmBase ref: references){
229 String message = null;
230 if (ref instanceof MediaRepresentation){
231 continue;
232 }else if (ref instanceof TextData){
233 TextData textData = HibernateProxyHelper.deproxy(ref, TextData.class);
234 DescriptionBase<?> description = HibernateProxyHelper.deproxy(textData.getInDescription(), DescriptionBase.class);
235
236 if (description instanceof TaxonDescription){
237 TaxonDescription desc = HibernateProxyHelper.deproxy(description, TaxonDescription.class);
238 if (desc.getTaxon() == null || (mediaConfig.isDeleteFromDescription() && (deleteFrom instanceof Taxon && ((Taxon)deleteFrom).getId() == desc.getTaxon().getId()))){
239 continue;
240 } else{
241 message = "The media can't be deleted from the database because it is referenced by a taxon. ("+desc.getTaxon().getTitleCache()+")";
242 result.setAbort();
243 }
244
245 } else if (description instanceof SpecimenDescription){
246 SpecimenDescription desc = HibernateProxyHelper.deproxy(description, SpecimenDescription.class);
247 if (desc.getDescribedSpecimenOrObservation() == null || (mediaConfig.isDeleteFromDescription() && (deleteFrom instanceof SpecimenOrObservationBase && ((SpecimenOrObservationBase)deleteFrom).getId() == desc.getDescribedSpecimenOrObservation().getId()))){
248 continue;
249 } else{
250 message = "The media can't be deleted from the database because it is referenced by a specimen or observation. ("+desc.getDescribedSpecimenOrObservation().getTitleCache()+")";
251 result.setAbort();
252 }
253 } else if (description instanceof TaxonNameDescription){
254 TaxonNameDescription desc = HibernateProxyHelper.deproxy(description, TaxonNameDescription.class);
255 if (desc.getTaxonName() == null || (mediaConfig.isDeleteFromDescription() && (deleteFrom instanceof TaxonName && ((TaxonName)deleteFrom).getId() == desc.getTaxonName().getId()))){
256 continue;
257 } else{
258 message = "The media can't be deleted from the database because it is referenced by a scientific name. ("+desc.getTaxonName().getTitleCache()+")";
259 result.setAbort();
260 }
261 }
262 }else if (ref instanceof MediaSpecimen){
263 message = "The media can't be deleted from the database because it is referenced by a mediaspecimen. ("+((MediaSpecimen)ref).getTitleCache()+")";
264 result.setAbort();
265 }else {
266 message = "The media can't be completely deleted because it is referenced by another " + ref.getUserFriendlyTypeName() ;
267 result.setAbort();
268 }
269 if (message != null){
270 result.addException(new ReferencedObjectUndeletableException(message));
271 result.addRelatedObject(ref);
272 }
273 }
274
275 return result;
276 }
277
278 /**
279 * Reads the metadata as stored in the file or web resource and filters the data by the include and exclude lists of key names
280 * as stored in the data base properties {@link PreferencePredicate#MediaMetadataKeynameExcludes} and {@link PreferencePredicate#MediaMetadataKeynameExcludes}
281 * <p>
282 * Metadata of multiple parts is merged into one common metadata map whereas the later part being read may overwrite data from previous parts.
283 * The consequences of this can be neglected since we don't expect that multiple parts are actually being used.
284 *
285 * @param representation
286 * @return
287 * @throws IOException
288 * @throws HttpException
289 */
290 @Override
291 public Map<String, String> readResourceMetadataFiltered(MediaRepresentation representation) throws IOException, HttpException {
292
293 List<String> includes = mediaMetadataKeyIncludes();
294 List<String> excludes = mediaMetadataKeyExludes();
295 Map<String, String> metadata = new HashMap<>();
296
297 for(MediaRepresentationPart part : representation.getParts()) {
298 CdmImageInfo iInfo = mediaInfoFactory.cdmImageInfoWithMetaData(part.getUri());
299 if(iInfo.getMetaData() != null) {
300 metadata.putAll(iInfo.getMetaData());
301 }
302 }
303
304 if(logger.isDebugEnabled()) {
305 logger.debug("meta data as read from all parts: " + metadata.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining(", ", "{", "}")));
306 }
307
308 if(!includes.isEmpty()) {
309 metadata = metadata.entrySet()
310 .stream()
311 .filter( e -> containsCaseInsensitive(e.getKey(), includes))
312 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
313 if(logger.isDebugEnabled()) {
314 logger.debug("meta filtered by includes: " + metadata.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining(", ", "{", "}")));
315 }
316 }
317 if(!excludes.isEmpty()) {
318 metadata = metadata.entrySet()
319 .stream()
320 .filter( e -> !containsCaseInsensitive(e.getKey(), excludes))
321 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
322 if(logger.isDebugEnabled()) {
323 logger.debug("meta filtered by excludes: " + metadata.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining(", ", "{", "}")));
324 }
325 }
326
327 if(metadata == null) {
328 metadata = new HashMap<>();
329 }
330 return metadata;
331 }
332
333 private boolean containsCaseInsensitive(String s, List<String> l){
334 return l.stream().anyMatch(x -> x.equalsIgnoreCase(s));
335 }
336
337 protected List<String> mediaMetadataKeyExludes(){
338 CdmPreference pref = prefsService.findExact(CdmPreference.NewKey(PreferenceSubject.NewDatabaseInstance(), PreferencePredicate.MediaMetadataKeynameExcludes));
339 if(pref == null) {
340 return new ArrayList<>();
341 }
342 return pref.splitStringListValue();
343 }
344
345 protected List<String> mediaMetadataKeyIncludes(){
346 CdmPreference pref = prefsService.findExact(CdmPreference.NewKey(PreferenceSubject.NewDatabaseInstance(), PreferencePredicate.MediaMetadataKeynameIncludes));
347 if(pref == null) {
348 return Arrays.asList(PreferencePredicate.MediaMetadataKeynameIncludes.getDefaultValue().toString().split(","));
349 }
350 return pref.splitStringListValue();
351 }
352 }