Project

General

Profile

« Previous | Next » 

Revision 6df5928c

Added by Andreas Müller over 1 year ago

cleanup

View differences:

cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/media/MediaRepresentationPart.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.model.media;
11

  
12
import java.util.HashSet;
13
import java.util.Set;
14

  
15
import javax.persistence.Entity;
16
import javax.persistence.FetchType;
17
import javax.persistence.JoinColumn;
18
import javax.persistence.ManyToOne;
19
import javax.persistence.OneToMany;
20
import javax.xml.bind.annotation.XmlAccessType;
21
import javax.xml.bind.annotation.XmlAccessorType;
22
import javax.xml.bind.annotation.XmlElement;
23
import javax.xml.bind.annotation.XmlElementWrapper;
24
import javax.xml.bind.annotation.XmlIDREF;
25
import javax.xml.bind.annotation.XmlSchemaType;
26
import javax.xml.bind.annotation.XmlType;
27

  
28
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;
29
import org.hibernate.annotations.Cascade;
30
import org.hibernate.annotations.CascadeType;
31
import org.hibernate.annotations.Type;
32
import org.hibernate.envers.Audited;
33
import org.hibernate.search.annotations.FieldBridge;
34

  
35
import eu.etaxonomy.cdm.common.URI;
36
import eu.etaxonomy.cdm.hibernate.search.UriBridge;
37
import eu.etaxonomy.cdm.model.common.VersionableEntity;
38

  
39
/**
40
 * A media representation part is a resource that can be referenced by an URI.
41
 * It represents a part of or the entire media. <br>
42
 * E.g. a jpg file or a website
43
 *
44
 * @author a.mueller
45
 * @since 09.06.2008
46
 */
47
@XmlAccessorType(XmlAccessType.FIELD)
48
@XmlType(name = "MediaRepresentationPart", propOrder = {
49
		"uri",
50
        "size",
51
        "mediaRepresentation",
52
        "mediaMetaData"
53
  })
54
@Entity
55
@Audited
56
public class MediaRepresentationPart extends VersionableEntity {
57
	private static final long serialVersionUID = -1674422508643785796L;
58
	@SuppressWarnings("unused")
59
	private static final Logger logger = LogManager.getLogger(MediaRepresentationPart.class);
60

  
61
	// where the media file is stored
62
	@XmlElement(name = "URI")
63
    @FieldBridge(impl = UriBridge.class)
64
	@Type(type="uriUserType")
65
	private URI uri;
66

  
67
	// in bytes
68
	@XmlElement(name = "Size")
69
	private Integer size;
70

  
71
	// the MediaRepresentation of this MediaRepresentationPart
72
	@XmlElement(name = "MediaRepresentation")
73
	@XmlIDREF
74
	@XmlSchemaType(name = "IDREF")
75
	@ManyToOne(fetch = FetchType.LAZY)
76
	@JoinColumn(name = "representation_id", nullable = false, updatable = false, insertable = false)
77
	@Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
78
	private MediaRepresentation mediaRepresentation;
79

  
80
    @XmlElementWrapper(name = "MediaMetaDatas")
81
    @XmlElement(name = "MediaMetaData")
82
    @OneToMany (mappedBy="mediaRepresentation", fetch= FetchType.LAZY, orphanRemoval=true)
83
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE, CascadeType.REFRESH})
84
	private Set<MediaMetaData> mediaMetaData = new HashSet<>();
85

  
86

  
87
// *************** FACTORY METHOD *********************************/
88

  
89
	public static MediaRepresentationPart NewInstance(URI uri, Integer size) {
90
		MediaRepresentationPart result = new MediaRepresentationPart(uri, size);
91
		return result;
92
	}
93

  
94
	protected MediaRepresentationPart() {
95
		super();
96
	}
97

  
98
	protected MediaRepresentationPart(URI uri, Integer size) {
99
		this();
100
		this.setUri(uri);
101
		this.setSize(size);
102
	}
103

  
104
//*************** GETTER / SETTER *************************************/
105

  
106
	public MediaRepresentation getMediaRepresentation() {
107
		return this.mediaRepresentation;
108
	}
109

  
110
	/**
111
	 *  @deprecated for internal (bidirectional) use only
112
	 */
113
	@Deprecated
114
	protected void setMediaRepresentation(MediaRepresentation mediaRepresentation) {
115
		this.mediaRepresentation = mediaRepresentation;
116
	}
117

  
118
	//uri
119
	public URI getUri() {
120
		return this.uri;
121
	}
122

  
123
	public void setUri(URI uri) {
124
		this.uri = uri;
125
	}
126

  
127
	//size
128
	public Integer getSize() {
129
		return this.size;
130
	}
131
	public void setSize(Integer size) {
132
		this.size = size;
133
	}
134

  
135
	//metadata
136

  
137

  
138
	public void addMediaMetaData(MediaMetaData metaData){
139
	    this.mediaMetaData.add(metaData);
140
	    if(metaData.getMediaRepresentation() != this){
141
	        metaData.setMediaRepresentation(this);
142
	    }
143
    }
144

  
145
    public Set<MediaMetaData> getMediaMetaData() {
146
        return mediaMetaData;
147
    }
148

  
149
//    public void setMediaMetaData(Set<MediaMetaData> mediaMetaData) {
150
//        this.mediaMetaData = mediaMetaData;
151
//    }
152

  
153
    public void removeMediaMetaData(MediaMetaData metaData){
154
        this.mediaMetaData.remove(metaData);
155
        if(metaData.getMediaRepresentation() == this){
156
            metaData.setMediaRepresentation(null);
157
        }
158
    }
159

  
160
//************************* CLONE **************************/
161

  
162
	@Override
163
	public MediaRepresentationPart clone() throws CloneNotSupportedException{
164
		MediaRepresentationPart result = (MediaRepresentationPart)super.clone();
165

  
166
		//media representation
167
		result.setMediaRepresentation(null);
168

  
169
		for (MediaMetaData metaData : this.getMediaMetaData()) {
170
		    result.addMediaMetaData(metaData.clone());
171
		}
172

  
173
		//no changes to: size, uri
174
		return result;
175
	}
176
}
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.model.media;
10

  
11
import java.util.HashSet;
12
import java.util.Set;
13

  
14
import javax.persistence.Entity;
15
import javax.persistence.FetchType;
16
import javax.persistence.JoinColumn;
17
import javax.persistence.ManyToOne;
18
import javax.persistence.OneToMany;
19
import javax.xml.bind.annotation.XmlAccessType;
20
import javax.xml.bind.annotation.XmlAccessorType;
21
import javax.xml.bind.annotation.XmlElement;
22
import javax.xml.bind.annotation.XmlElementWrapper;
23
import javax.xml.bind.annotation.XmlIDREF;
24
import javax.xml.bind.annotation.XmlSchemaType;
25
import javax.xml.bind.annotation.XmlType;
26

  
27
import org.apache.logging.log4j.LogManager;
28
import org.apache.logging.log4j.Logger;
29
import org.hibernate.annotations.Cascade;
30
import org.hibernate.annotations.CascadeType;
31
import org.hibernate.annotations.Type;
32
import org.hibernate.envers.Audited;
33
import org.hibernate.search.annotations.FieldBridge;
34

  
35
import eu.etaxonomy.cdm.common.URI;
36
import eu.etaxonomy.cdm.hibernate.search.UriBridge;
37
import eu.etaxonomy.cdm.model.common.VersionableEntity;
38

  
39
/**
40
 * A media representation part is a resource that can be referenced by an URI.
41
 * It represents a part of or the entire media. <br>
42
 * E.g. a jpg file or a website
43
 *
44
 * @author a.mueller
45
 * @since 09.06.2008
46
 */
47
@XmlAccessorType(XmlAccessType.FIELD)
48
@XmlType(name = "MediaRepresentationPart", propOrder = {
49
		"uri",
50
        "size",
51
        "mediaRepresentation",
52
        "mediaMetaData"
53
  })
54
@Entity
55
@Audited
56
public class MediaRepresentationPart extends VersionableEntity {
57
	private static final long serialVersionUID = -1674422508643785796L;
58
	@SuppressWarnings("unused")
59
	private static final Logger logger = LogManager.getLogger(MediaRepresentationPart.class);
60

  
61
	// where the media file is stored
62
	@XmlElement(name = "URI")
63
    @FieldBridge(impl = UriBridge.class)
64
	@Type(type="uriUserType")
65
	private URI uri;
66

  
67
	// in bytes
68
	@XmlElement(name = "Size")
69
	private Integer size;
70

  
71
	// the MediaRepresentation of this MediaRepresentationPart
72
	@XmlElement(name = "MediaRepresentation")
73
	@XmlIDREF
74
	@XmlSchemaType(name = "IDREF")
75
	@ManyToOne(fetch = FetchType.LAZY)
76
	@JoinColumn(name = "representation_id", nullable = false, updatable = false, insertable = false)
77
	@Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
78
	private MediaRepresentation mediaRepresentation;
79

  
80
    @XmlElementWrapper(name = "MediaMetaDatas")
81
    @XmlElement(name = "MediaMetaData")
82
    @OneToMany (mappedBy="mediaRepresentation", fetch= FetchType.LAZY, orphanRemoval=true)
83
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE, CascadeType.REFRESH})
84
	private Set<MediaMetaData> mediaMetaData = new HashSet<>();
85

  
86

  
87
// *************** FACTORY METHOD *********************************/
88

  
89
	public static MediaRepresentationPart NewInstance(URI uri, Integer size) {
90
		MediaRepresentationPart result = new MediaRepresentationPart(uri, size);
91
		return result;
92
	}
93

  
94
	protected MediaRepresentationPart() {
95
		super();
96
	}
97

  
98
	protected MediaRepresentationPart(URI uri, Integer size) {
99
		this();
100
		this.setUri(uri);
101
		this.setSize(size);
102
	}
103

  
104
//*************** GETTER / SETTER *************************************/
105

  
106
	public MediaRepresentation getMediaRepresentation() {
107
		return this.mediaRepresentation;
108
	}
109

  
110
	/**
111
	 *  @deprecated for internal (bidirectional) use only
112
	 */
113
	@Deprecated
114
	protected void setMediaRepresentation(MediaRepresentation mediaRepresentation) {
115
		this.mediaRepresentation = mediaRepresentation;
116
	}
117

  
118
	//uri
119
	public URI getUri() {
120
		return this.uri;
121
	}
122

  
123
	public void setUri(URI uri) {
124
		this.uri = uri;
125
	}
126

  
127
	//size
128
	public Integer getSize() {
129
		return this.size;
130
	}
131
	public void setSize(Integer size) {
132
		this.size = size;
133
	}
134

  
135
	//metadata
136

  
137

  
138
	public void addMediaMetaData(MediaMetaData metaData){
139
	    this.mediaMetaData.add(metaData);
140
	    if(metaData.getMediaRepresentation() != this){
141
	        metaData.setMediaRepresentation(this);
142
	    }
143
    }
144

  
145
    public Set<MediaMetaData> getMediaMetaData() {
146
        return mediaMetaData;
147
    }
148

  
149
//    public void setMediaMetaData(Set<MediaMetaData> mediaMetaData) {
150
//        this.mediaMetaData = mediaMetaData;
151
//    }
152

  
153
    public void removeMediaMetaData(MediaMetaData metaData){
154
        this.mediaMetaData.remove(metaData);
155
        if(metaData.getMediaRepresentation() == this){
156
            metaData.setMediaRepresentation(null);
157
        }
158
    }
159

  
160
//************************* CLONE **************************/
161

  
162
	@Override
163
	public MediaRepresentationPart clone() throws CloneNotSupportedException{
164
		MediaRepresentationPart result = (MediaRepresentationPart)super.clone();
165

  
166
		//media representation
167
		result.setMediaRepresentation(null);
168

  
169
		for (MediaMetaData metaData : this.getMediaMetaData()) {
170
		    result.addMediaMetaData(metaData.clone());
171
		}
172

  
173
		//no changes to: size, uri
174
		return result;
175
	}
176
}
cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/media/MediaUtils.java
1
package eu.etaxonomy.cdm.model.media;
2

  
3
import java.awt.Dimension;
4
import java.util.ArrayList;
5
import java.util.HashMap;
6
import java.util.LinkedHashMap;
7
import java.util.LinkedHashSet;
8
import java.util.List;
9
import java.util.Map;
10
import java.util.Set;
11
import java.util.SortedMap;
12
import java.util.TreeMap;
13
import java.util.regex.Pattern;
14
import java.util.stream.Collectors;
15

  
16
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;
17

  
18
import eu.etaxonomy.cdm.model.common.CdmBase;
19

  
20
public class MediaUtils {
21

  
22
    private static final Logger logger = LogManager.getLogger(MediaUtils.class);
23

  
24

  
25
    public static MediaRepresentation findBestMatchingRepresentation(Media media,
26
            Class<? extends MediaRepresentationPart> representationPartType, Integer size, Integer height,
27
            Integer widthOrDuration, String[] mimeTypes, MissingValueStrategy missingValStrategy){
28

  
29
        // find best matching representations of each media
30
        Set<MediaRepresentation> representations = media.getRepresentations();
31
        return findBestMatchingRepresentation(representations, representationPartType, size, height, widthOrDuration,
32
                mimeTypes, missingValStrategy);
33
    }
34

  
35
    public static MediaRepresentation findBestMatchingRepresentation(
36
            Set<MediaRepresentation> representations, Class<? extends MediaRepresentationPart> representationPartType, Integer size,
37
            Integer height, Integer widthOrDuration, String[] mimeTypes,
38
            MissingValueStrategy missingValStrategy) {
39

  
40
        SortedMap<Long, MediaRepresentation> prefRepresentations
41
                = filterAndOrderMediaRepresentations(representations, representationPartType, mimeTypes,
42
                        size, widthOrDuration, height, missingValStrategy);
43
        if(prefRepresentations.size() > 0){
44
            MediaRepresentation prefOne = prefRepresentations.get(prefRepresentations.firstKey());
45
            return prefOne;
46
        }
47
        return null;
48
    }
49

  
50
    /**
51
     * Return the first {@link MediaRepresentationPart} found for the given {@link Media}
52
     * or <code>null</code> otherwise.
53
     * @param media the media which is searched for the first part
54
     * @return the first part found or <code>null</code>
55
     */
56
    public static MediaRepresentationPart getFirstMediaRepresentationPart(Media media){
57
        if(media==null){
58
            return null;
59
        }
60
        MediaRepresentationPart mediaRepresentationPart = null;
61
        Set<MediaRepresentation> representations = media.getRepresentations();
62
        if(representations!=null && representations.size()>0){
63
            MediaRepresentation mediaRepresentation = representations.iterator().next();
64
            List<MediaRepresentationPart> parts = mediaRepresentation.getParts();
65
            if(parts!=null && parts.size()>0){
66
                mediaRepresentationPart = parts.iterator().next();
67
            }
68
        }
69
        return mediaRepresentationPart;
70
    }
71

  
72
    /**
73
     * Creates one single {@link MediaRepresentationPart} for the given {@link Media}
74
     * if it does not already exists. Otherwise the first part found is returned.<br>
75
     * @param media the media for which the representation part should be created
76
     * @return the first or newly created representation part
77
     */
78
    public static MediaRepresentationPart initFirstMediaRepresentationPart(Media media, boolean isImage) {
79
        MediaRepresentationPart mediaRepresentationPart = getFirstMediaRepresentationPart(media);
80
        if(mediaRepresentationPart==null){
81
            Set<MediaRepresentation> representations = media.getRepresentations();
82
            if(representations!=null && representations.size()>0){
83
                MediaRepresentation mediaRepresentation = representations.iterator().next();
84
                if(isImage){
85
                    mediaRepresentationPart = ImageFile.NewInstance(null, null);
86
                }
87
                else{
88
                    mediaRepresentationPart = MediaRepresentationPart.NewInstance(null, null);
89
                }
90
                mediaRepresentation.addRepresentationPart(mediaRepresentationPart);
91
            }
92
            else{
93
                if(isImage){
94
                    mediaRepresentationPart = ImageFile.NewInstance(null, null);
95
                }
96
                else{
97
                    mediaRepresentationPart = MediaRepresentationPart.NewInstance(null, null);
98
                }
99

  
100
                MediaRepresentation mediaRepresentation = MediaRepresentation.NewInstance();
101
                mediaRepresentation.addRepresentationPart(mediaRepresentationPart);
102
                media.addRepresentation(mediaRepresentation);
103
            }
104
        }
105
        return mediaRepresentationPart;
106
    }
107

  
108
    /**
109
     * Filters the given List of Media by the supplied filter parameters <code>representationPartType</code>,
110
     * <code>mimeTypes</code>, <code>widthOrDuration</code>, <code>height</code>, <code>size</code>.
111
     * Only best matching MediaRepresentation remains attached to the Media entities.
112
     * A Media entity may be completely omitted in the resulting list if  {@link #filterAndOrderMediaRepresentations(Set, Class, String[], Integer, Integer, Integer)}
113
     * is not returning any matching representation. This can be the case if a <code>representationPartType</code> is supplied.
114
     *
115
     * @param mediaList
116
     * @param representationPartType any subclass of {@link MediaRepresentationPart}
117
     * @param mimeTypes
118
     * @param widthOrDuration
119
     * @param height
120
     * @param size
121
     * @return
122
     */
123
    public static Map<Media, MediaRepresentation> findPreferredMedia(List<Media> mediaList,
124
            Class<? extends MediaRepresentationPart> representationPartType, String[] mimeTypes, Integer widthOrDuration,
125
            Integer height, Integer size, MissingValueStrategy missingValStrat) {
126

  
127
        if(mimeTypes != null) {
128
            for(int i=0; i<mimeTypes.length; i++){
129
                mimeTypes[i] = mimeTypes[i].replace(':', '/');
130
            }
131
        }
132

  
133
        Map<Media, MediaRepresentation> returnMediaList;
134
        if(mediaList != null){
135
            returnMediaList = new LinkedHashMap<>(mediaList.size());
136
            for(Media media : mediaList){
137

  
138
                Set<MediaRepresentation> candidateRepresentations = new LinkedHashSet<>();
139
                candidateRepresentations.addAll(media.getRepresentations());
140

  
141
                SortedMap<Long, MediaRepresentation> prefRepresentations
142
                    = filterAndOrderMediaRepresentations(candidateRepresentations, representationPartType,
143
                            mimeTypes, size, widthOrDuration, height, missingValStrat);
144

  
145
                if(prefRepresentations.size() > 0){
146
                    // Media.representations is a set
147
                    // so it cannot retain the sorting which has been found by filterAndOrderMediaRepresentations()
148
                    // thus we take first one and remove all other representations
149
                    returnMediaList.put(media, prefRepresentations.get(prefRepresentations.firstKey()));
150
                }
151

  
152
            }
153
        }
154
        else{
155
            returnMediaList = new HashMap<>();
156
        }
157
        return returnMediaList;
158
    }
159

  
160
    /**
161
     * @see also cdm-dataportal: cdm-api.module#cdm_preferred_media_representations()
162
     *
163
     * @param mediaRepresentations
164
     * @param representationPartType
165
     * @param mimeTypeRegexes
166
     * @param size
167
     *  Applies to all {@link MediaRepresentationPart}s (value = <code>null</code> means ignore, for maximum size use {@link Integer#MAX_VALUE})
168
     * @param widthOrDuration
169
     *   Applied to {@link ImageFile#getWidth()}, or {@link {@link MovieFile#getDuration()},
170
     *   or {@link {@link AudioFile#getDuration()} (value = <code>null</code> means ignore,
171
     *   for maximum use {@link Integer#MAX_VALUE})
172
     * @param height
173
     *   The height is only applied to {@link ImageFile}s (value = <code>null</code> means ignore,
174
     *   for maximum height use {@link Integer#MAX_VALUE})
175
     * @return
176
     */
177
    public static SortedMap<Long, MediaRepresentation> filterAndOrderMediaRepresentations(
178
            Set<MediaRepresentation> mediaRepresentations,
179
            Class<? extends MediaRepresentationPart> representationPartType, String[] mimeTypeRegexes,
180
            Integer size, Integer widthOrDuration, Integer height,
181
            MissingValueStrategy missingValStrat) {
182

  
183
        SortedMap<Long, MediaRepresentation> prefRepr = new TreeMap<>();
184

  
185
        Dimension preferredImageDimensions = dimensionsFilter(widthOrDuration, height, null);
186
        long preferredExpansion = expanse(preferredImageDimensions);
187
        logger.debug("preferredExpansion: " + preferredExpansion);
188

  
189
        mimeTypeRegexes = (mimeTypeRegexes == null ? new String[]{".*"} : mimeTypeRegexes);
190

  
191
        for (String mimeTypeRegex : mimeTypeRegexes) {
192
            // getRepresentationByMimeType
193
            Pattern mimeTypePattern = Pattern.compile(mimeTypeRegex);
194
            int representationCnt = 0;
195
            for (MediaRepresentation representation : mediaRepresentations) {
196

  
197
                List<MediaRepresentationPart> matchingParts = new ArrayList<>();
198

  
199

  
200
                // check MIME type
201
                boolean isMimeTypeMatch = representation.getMimeType() == null
202
                        || mimeTypePattern.matcher(representation.getMimeType()).matches();
203
                if(logger.isDebugEnabled()){
204
                    logger.debug("isMimeTypeMatch: " + Boolean.valueOf(isMimeTypeMatch).toString());
205
                }
206

  
207
                long dimensionsDeltaAllParts = 0;
208

  
209
                //first the size is used for comparison
210
                for (MediaRepresentationPart part : representation.getParts()) {
211

  
212
                    // check representationPartType
213
                    boolean isRepresentationPartTypeMatch = representationPartType == null
214
                            || part.getClass().isAssignableFrom(representationPartType);
215
                    if(logger.isDebugEnabled()){
216
                        logger.debug("isRepresentationPartTypeMatch: " + Boolean.valueOf(isRepresentationPartTypeMatch).toString());
217
                    }
218

  
219
                    if ( !(isRepresentationPartTypeMatch && isMimeTypeMatch) ) {
220
                        continue;
221
                    }
222

  
223
                    if(logger.isDebugEnabled()){
224
                        logger.debug(part + " matches");
225
                    }
226
                    matchingParts.add(part);
227

  
228
                    Integer sizeOfPart = part.getSize();
229
                    if(isUndefined(sizeOfPart)){
230
                        sizeOfPart = missingValStrat.applyTo(sizeOfPart);
231
                    }
232
                    if (size != null && sizeOfPart != null){
233
                        int distance = sizeOfPart - size;
234
                        if (distance < 0) {
235
                            distance *= -1;
236
                        }
237
                        dimensionsDeltaAllParts += distance;
238

  
239
                    }
240

  
241
                    //if height and width/duration is defined, add this information, too
242
                    if (preferredImageDimensions != null || widthOrDuration != null){
243
                        long expansionDelta = 0;
244
                        if (part.isInstanceOf(ImageFile.class)) {
245
                            if (preferredImageDimensions != null){
246
                                ImageFile image = CdmBase.deproxy(part, ImageFile.class);
247
                                Dimension imageDimension = dimensionsFilter(image.getWidth(), image.getHeight(), missingValStrat);
248
                                if (imageDimension != null){
249
                                    expansionDelta = Math.abs(expanse(imageDimension) - preferredExpansion);
250
                                }
251
                                if(logger.isDebugEnabled()){
252
                                    if(logger.isDebugEnabled()){
253
                                        logger.debug("part [" + part.getUri() + "; " + imageDimension + "] : preferredImageDimensions= " + preferredImageDimensions + ", size= "  + size+ " >>" + expansionDelta );
254
                                    }
255
                                }
256
                            }
257
                        }
258
                        else if (part.isInstanceOf(MovieFile.class)){
259
                             MovieFile movie = CdmBase.deproxy(part, MovieFile.class);
260
                             Integer durationOfMovie = movie.getDuration();
261
                             if(isUndefined(durationOfMovie)){
262
                                 durationOfMovie = null; // convert potential 0 to null!
263
                             }
264
                             durationOfMovie = missingValStrat.applyTo(durationOfMovie);
265
                             if(widthOrDuration != null){
266
                                expansionDelta = durationOfMovie - widthOrDuration;
267
                            }
268
                             if(logger.isDebugEnabled()){
269
                                 logger.debug("part MovieFile[" + part.getUri() + "; duration=" + movie.getDuration() + "-> " + durationOfMovie + "] : preferrdDuration= " + widthOrDuration + ", size= "  + size+ " >>" + expansionDelta );
270
                             }
271
                        } else if (part.isInstanceOf(AudioFile.class)){
272
                            AudioFile audio = CdmBase.deproxy(part, AudioFile.class);
273
                            Integer durationOfAudio = audio.getDuration();
274
                            if(isUndefined(durationOfAudio)){
275
                                durationOfAudio = null;  // convert potential 0 to null!
276
                            }
277
                            durationOfAudio = missingValStrat.applyTo(durationOfAudio);
278
                            if(widthOrDuration != null) {
279
                                expansionDelta = durationOfAudio - widthOrDuration;
280
                            }
281
                            if(logger.isDebugEnabled()){
282
                                logger.debug("part AudioFile[" + part.getUri() + "; duration=" +  audio.getDuration() + "-> " + durationOfAudio + "] : preferrdDuration= " + widthOrDuration + ", size= "  + size + " >>" + expansionDelta );
283
                            }
284
                        }
285
                        // the expansionDelta is summed up since the parts together for the whole
286
                        // which is bigger than only a part. By simply summing up images splitted
287
                        // into parts have too much weight compared to the single image but since
288
                        // parts are not used at all this is currently not a problem
289
                        dimensionsDeltaAllParts += expansionDelta;
290

  
291
                    }
292
                } // loop parts
293
                logger.debug("matchingParts.size():" + matchingParts.size());
294
                if(matchingParts.size() > 0 ){
295
                    representation.getParts().clear();
296
                    representation.getParts().addAll(matchingParts);
297
                    prefRepr.put((dimensionsDeltaAllParts + representationCnt++), representation);
298
                }
299
            } // loop representations
300
        } // loop mime types
301
        if(logger.isDebugEnabled()){
302
            String text =  prefRepr.keySet().stream()
303
            .map(key -> key + ": " + prefRepr.get(key).getParts().get(0).getUri().toString())
304
            .collect(Collectors.joining(", ", "{", "}"));
305
            logger.debug("resulting representations: " + text);
306
        }
307

  
308

  
309
        return prefRepr;
310

  
311
    }
312

  
313
    /**
314
     * @param imageDimension
315
     * @return
316
     */
317
    static long expanse(Dimension imageDimension) {
318
        if(imageDimension != null){
319
            return (long)imageDimension.height * (long)imageDimension.width;
320
        } else {
321
            return -1;
322
        }
323
    }
324

  
325
    /**
326
     * @param widthOrDuration
327
     * @param height
328
     * @param mvs Will be applied when both, width and height, are <code>null</code>
329
     */
330
    static Dimension dimensionsFilter(Integer width, Integer height, MissingValueStrategy mvs) {
331
        Dimension imageDimensions = null;
332
        if(!isUndefined(height) || !isUndefined(width)){
333
            imageDimensions = new Dimension();
334
            if (!isUndefined(height) && isUndefined(width)){
335
                imageDimensions.setSize(1, height);
336
            } else if(isUndefined(height) && !isUndefined(width)) {
337
                imageDimensions.setSize(width, 1); // --> height will be respected and width is ignored
338
            } else {
339
                imageDimensions.setSize(width, height);
340
            }
341
        } else {
342
            // both, width and height, are undefined
343

  
344
            if(mvs != null){
345
                // set both values to null so that the MissingValueStrategy can be applied
346
                // the MissingValueStrategy only get effective when the supplied value  is NULL
347
                width = null;
348
                height = null;
349
                imageDimensions = new Dimension(mvs.applyTo(width), mvs.applyTo(height));
350
            }
351
        }
352
        return imageDimensions;
353
    }
354

  
355
    static private boolean isUndefined(Integer val) {
356
        return val == null || val == 0;
357
    }
358

  
359
    /**
360
     * Strategies for replacing <code>null</code> values with a numeric value.
361
     *
362
     * @author a.kohlbecker
363
     */
364
    public enum MissingValueStrategy {
365
        /**
366
         * replace <code>null</code> by {@link Integer#MAX_VALUE}
367
         */
368
        MAX(Integer.MAX_VALUE),
369
        /**
370
         * replace <code>null</code> by <code>0</code>
371
         */
372
        ZERO(0);
373

  
374
        private Integer defaultValue;
375

  
376
        MissingValueStrategy(Integer defaultValue){
377
            this.defaultValue = defaultValue;
378
        }
379

  
380
        public Integer applyTo(Integer val){
381
            if(val == null){
382
                return defaultValue;
383
            } else {
384
                return val;
385
            }
386
        }
387
    }
388
  }
1
package eu.etaxonomy.cdm.model.media;
2

  
3
import java.awt.Dimension;
4
import java.util.ArrayList;
5
import java.util.HashMap;
6
import java.util.LinkedHashMap;
7
import java.util.LinkedHashSet;
8
import java.util.List;
9
import java.util.Map;
10
import java.util.Set;
11
import java.util.SortedMap;
12
import java.util.TreeMap;
13
import java.util.regex.Pattern;
14
import java.util.stream.Collectors;
15

  
16
import org.apache.logging.log4j.LogManager;
17
import org.apache.logging.log4j.Logger;
18

  
19
import eu.etaxonomy.cdm.model.common.CdmBase;
20

  
21
public class MediaUtils {
22

  
23
    private static final Logger logger = LogManager.getLogger();
24

  
25

  
26
    public static MediaRepresentation findBestMatchingRepresentation(Media media,
27
            Class<? extends MediaRepresentationPart> representationPartType, Integer size, Integer height,
28
            Integer widthOrDuration, String[] mimeTypes, MissingValueStrategy missingValStrategy){
29

  
30
        // find best matching representations of each media
31
        Set<MediaRepresentation> representations = media.getRepresentations();
32
        return findBestMatchingRepresentation(representations, representationPartType, size, height, widthOrDuration,
33
                mimeTypes, missingValStrategy);
34
    }
35

  
36
    public static MediaRepresentation findBestMatchingRepresentation(
37
            Set<MediaRepresentation> representations, Class<? extends MediaRepresentationPart> representationPartType, Integer size,
38
            Integer height, Integer widthOrDuration, String[] mimeTypes,
39
            MissingValueStrategy missingValStrategy) {
40

  
41
        SortedMap<Long, MediaRepresentation> prefRepresentations
42
                = filterAndOrderMediaRepresentations(representations, representationPartType, mimeTypes,
43
                        size, widthOrDuration, height, missingValStrategy);
44
        if(prefRepresentations.size() > 0){
45
            MediaRepresentation prefOne = prefRepresentations.get(prefRepresentations.firstKey());
46
            return prefOne;
47
        }
48
        return null;
49
    }
50

  
51
    /**
52
     * Return the first {@link MediaRepresentationPart} found for the given {@link Media}
53
     * or <code>null</code> otherwise.
54
     * @param media the media which is searched for the first part
55
     * @return the first part found or <code>null</code>
56
     */
57
    public static MediaRepresentationPart getFirstMediaRepresentationPart(Media media){
58
        if(media==null){
59
            return null;
60
        }
61
        MediaRepresentationPart mediaRepresentationPart = null;
62
        Set<MediaRepresentation> representations = media.getRepresentations();
63
        if(representations!=null && representations.size()>0){
64
            MediaRepresentation mediaRepresentation = representations.iterator().next();
65
            List<MediaRepresentationPart> parts = mediaRepresentation.getParts();
66
            if(parts!=null && parts.size()>0){
67
                mediaRepresentationPart = parts.iterator().next();
68
            }
69
        }
70
        return mediaRepresentationPart;
71
    }
72

  
73
    /**
74
     * Creates one single {@link MediaRepresentationPart} for the given {@link Media}
75
     * if it does not already exists. Otherwise the first part found is returned.<br>
76
     * @param media the media for which the representation part should be created
77
     * @return the first or newly created representation part
78
     */
79
    public static MediaRepresentationPart initFirstMediaRepresentationPart(Media media, boolean isImage) {
80
        MediaRepresentationPart mediaRepresentationPart = getFirstMediaRepresentationPart(media);
81
        if(mediaRepresentationPart==null){
82
            Set<MediaRepresentation> representations = media.getRepresentations();
83
            if(representations!=null && representations.size()>0){
84
                MediaRepresentation mediaRepresentation = representations.iterator().next();
85
                if(isImage){
86
                    mediaRepresentationPart = ImageFile.NewInstance(null, null);
87
                }
88
                else{
89
                    mediaRepresentationPart = MediaRepresentationPart.NewInstance(null, null);
90
                }
91
                mediaRepresentation.addRepresentationPart(mediaRepresentationPart);
92
            }
93
            else{
94
                if(isImage){
95
                    mediaRepresentationPart = ImageFile.NewInstance(null, null);
96
                }
97
                else{
98
                    mediaRepresentationPart = MediaRepresentationPart.NewInstance(null, null);
99
                }
100

  
101
                MediaRepresentation mediaRepresentation = MediaRepresentation.NewInstance();
102
                mediaRepresentation.addRepresentationPart(mediaRepresentationPart);
103
                media.addRepresentation(mediaRepresentation);
104
            }
105
        }
106
        return mediaRepresentationPart;
107
    }
108

  
109
    /**
110
     * Filters the given List of Media by the supplied filter parameters <code>representationPartType</code>,
111
     * <code>mimeTypes</code>, <code>widthOrDuration</code>, <code>height</code>, <code>size</code>.
112
     * Only best matching MediaRepresentation remains attached to the Media entities.
113
     * A Media entity may be completely omitted in the resulting list if  {@link #filterAndOrderMediaRepresentations(Set, Class, String[], Integer, Integer, Integer)}
114
     * is not returning any matching representation. This can be the case if a <code>representationPartType</code> is supplied.
115
     *
116
     * @param mediaList
117
     * @param representationPartType any subclass of {@link MediaRepresentationPart}
118
     * @param mimeTypes
119
     * @param widthOrDuration
120
     * @param height
121
     * @param size
122
     * @return
123
     */
124
    public static Map<Media, MediaRepresentation> findPreferredMedia(List<Media> mediaList,
125
            Class<? extends MediaRepresentationPart> representationPartType, String[] mimeTypes, Integer widthOrDuration,
126
            Integer height, Integer size, MissingValueStrategy missingValStrat) {
127

  
128
        if(mimeTypes != null) {
129
            for(int i=0; i<mimeTypes.length; i++){
130
                mimeTypes[i] = mimeTypes[i].replace(':', '/');
131
            }
132
        }
133

  
134
        Map<Media, MediaRepresentation> returnMediaList;
135
        if(mediaList != null){
136
            returnMediaList = new LinkedHashMap<>(mediaList.size());
137
            for(Media media : mediaList){
138

  
139
                Set<MediaRepresentation> candidateRepresentations = new LinkedHashSet<>();
140
                candidateRepresentations.addAll(media.getRepresentations());
141

  
142
                SortedMap<Long, MediaRepresentation> prefRepresentations
143
                    = filterAndOrderMediaRepresentations(candidateRepresentations, representationPartType,
144
                            mimeTypes, size, widthOrDuration, height, missingValStrat);
145

  
146
                if(prefRepresentations.size() > 0){
147
                    // Media.representations is a set
148
                    // so it cannot retain the sorting which has been found by filterAndOrderMediaRepresentations()
149
                    // thus we take first one and remove all other representations
150
                    returnMediaList.put(media, prefRepresentations.get(prefRepresentations.firstKey()));
151
                }
152

  
153
            }
154
        }
155
        else{
156
            returnMediaList = new HashMap<>();
157
        }
158
        return returnMediaList;
159
    }
160

  
161
    /**
162
     * @see also cdm-dataportal: cdm-api.module#cdm_preferred_media_representations()
163
     *
164
     * @param mediaRepresentations
165
     * @param representationPartType
166
     * @param mimeTypeRegexes
167
     * @param size
168
     *  Applies to all {@link MediaRepresentationPart}s (value = <code>null</code> means ignore, for maximum size use {@link Integer#MAX_VALUE})
169
     * @param widthOrDuration
170
     *   Applied to {@link ImageFile#getWidth()}, or {@link {@link MovieFile#getDuration()},
171
     *   or {@link {@link AudioFile#getDuration()} (value = <code>null</code> means ignore,
172
     *   for maximum use {@link Integer#MAX_VALUE})
173
     * @param height
174
     *   The height is only applied to {@link ImageFile}s (value = <code>null</code> means ignore,
175
     *   for maximum height use {@link Integer#MAX_VALUE})
176
     * @return
177
     */
178
    public static SortedMap<Long, MediaRepresentation> filterAndOrderMediaRepresentations(
179
            Set<MediaRepresentation> mediaRepresentations,
180
            Class<? extends MediaRepresentationPart> representationPartType, String[] mimeTypeRegexes,
181
            Integer size, Integer widthOrDuration, Integer height,
182
            MissingValueStrategy missingValStrat) {
183

  
184
        SortedMap<Long, MediaRepresentation> prefRepr = new TreeMap<>();
185

  
186
        Dimension preferredImageDimensions = dimensionsFilter(widthOrDuration, height, null);
187
        long preferredExpansion = expanse(preferredImageDimensions);
188
        logger.debug("preferredExpansion: " + preferredExpansion);
189

  
190
        mimeTypeRegexes = (mimeTypeRegexes == null ? new String[]{".*"} : mimeTypeRegexes);
191

  
192
        for (String mimeTypeRegex : mimeTypeRegexes) {
193
            // getRepresentationByMimeType
194
            Pattern mimeTypePattern = Pattern.compile(mimeTypeRegex);
195
            int representationCnt = 0;
196
            for (MediaRepresentation representation : mediaRepresentations) {
197

  
198
                List<MediaRepresentationPart> matchingParts = new ArrayList<>();
199

  
200

  
201
                // check MIME type
202
                boolean isMimeTypeMatch = representation.getMimeType() == null
203
                        || mimeTypePattern.matcher(representation.getMimeType()).matches();
204
                if(logger.isDebugEnabled()){
205
                    logger.debug("isMimeTypeMatch: " + Boolean.valueOf(isMimeTypeMatch).toString());
206
                }
207

  
208
                long dimensionsDeltaAllParts = 0;
209

  
210
                //first the size is used for comparison
211
                for (MediaRepresentationPart part : representation.getParts()) {
212

  
213
                    // check representationPartType
214
                    boolean isRepresentationPartTypeMatch = representationPartType == null
215
                            || part.getClass().isAssignableFrom(representationPartType);
216
                    if(logger.isDebugEnabled()){
217
                        logger.debug("isRepresentationPartTypeMatch: " + Boolean.valueOf(isRepresentationPartTypeMatch).toString());
218
                    }
219

  
220
                    if ( !(isRepresentationPartTypeMatch && isMimeTypeMatch) ) {
221
                        continue;
222
                    }
223

  
224
                    if(logger.isDebugEnabled()){
225
                        logger.debug(part + " matches");
226
                    }
227
                    matchingParts.add(part);
228

  
229
                    Integer sizeOfPart = part.getSize();
230
                    if(isUndefined(sizeOfPart)){
231
                        sizeOfPart = missingValStrat.applyTo(sizeOfPart);
232
                    }
233
                    if (size != null && sizeOfPart != null){
234
                        int distance = sizeOfPart - size;
235
                        if (distance < 0) {
236
                            distance *= -1;
237
                        }
238
                        dimensionsDeltaAllParts += distance;
239

  
240
                    }
241

  
242
                    //if height and width/duration is defined, add this information, too
243
                    if (preferredImageDimensions != null || widthOrDuration != null){
244
                        long expansionDelta = 0;
245
                        if (part.isInstanceOf(ImageFile.class)) {
246
                            if (preferredImageDimensions != null){
247
                                ImageFile image = CdmBase.deproxy(part, ImageFile.class);
248
                                Dimension imageDimension = dimensionsFilter(image.getWidth(), image.getHeight(), missingValStrat);
249
                                if (imageDimension != null){
250
                                    expansionDelta = Math.abs(expanse(imageDimension) - preferredExpansion);
251
                                }
252
                                if(logger.isDebugEnabled()){
253
                                    if(logger.isDebugEnabled()){
254
                                        logger.debug("part [" + part.getUri() + "; " + imageDimension + "] : preferredImageDimensions= " + preferredImageDimensions + ", size= "  + size+ " >>" + expansionDelta );
255
                                    }
256
                                }
257
                            }
258
                        }
259
                        else if (part.isInstanceOf(MovieFile.class)){
260
                             MovieFile movie = CdmBase.deproxy(part, MovieFile.class);
261
                             Integer durationOfMovie = movie.getDuration();
262
                             if(isUndefined(durationOfMovie)){
263
                                 durationOfMovie = null; // convert potential 0 to null!
264
                             }
265
                             durationOfMovie = missingValStrat.applyTo(durationOfMovie);
266
                             if(widthOrDuration != null){
267
                                expansionDelta = durationOfMovie - widthOrDuration;
268
                            }
269
                             if(logger.isDebugEnabled()){
270
                                 logger.debug("part MovieFile[" + part.getUri() + "; duration=" + movie.getDuration() + "-> " + durationOfMovie + "] : preferrdDuration= " + widthOrDuration + ", size= "  + size+ " >>" + expansionDelta );
271
                             }
272
                        } else if (part.isInstanceOf(AudioFile.class)){
273
                            AudioFile audio = CdmBase.deproxy(part, AudioFile.class);
274
                            Integer durationOfAudio = audio.getDuration();
275
                            if(isUndefined(durationOfAudio)){
276
                                durationOfAudio = null;  // convert potential 0 to null!
277
                            }
278
                            durationOfAudio = missingValStrat.applyTo(durationOfAudio);
279
                            if(widthOrDuration != null) {
280
                                expansionDelta = durationOfAudio - widthOrDuration;
281
                            }
282
                            if(logger.isDebugEnabled()){
283
                                logger.debug("part AudioFile[" + part.getUri() + "; duration=" +  audio.getDuration() + "-> " + durationOfAudio + "] : preferrdDuration= " + widthOrDuration + ", size= "  + size + " >>" + expansionDelta );
284
                            }
285
                        }
286
                        // the expansionDelta is summed up since the parts together for the whole
287
                        // which is bigger than only a part. By simply summing up images splitted
288
                        // into parts have too much weight compared to the single image but since
289
                        // parts are not used at all this is currently not a problem
290
                        dimensionsDeltaAllParts += expansionDelta;
291

  
292
                    }
293
                } // loop parts
294
                logger.debug("matchingParts.size():" + matchingParts.size());
295
                if(matchingParts.size() > 0 ){
296
                    representation.getParts().clear();
297
                    representation.getParts().addAll(matchingParts);
298
                    prefRepr.put((dimensionsDeltaAllParts + representationCnt++), representation);
299
                }
300
            } // loop representations
301
        } // loop mime types
302
        if(logger.isDebugEnabled()){
303
            String text =  prefRepr.keySet().stream()
304
            .map(key -> key + ": " + prefRepr.get(key).getParts().get(0).getUri().toString())
305
            .collect(Collectors.joining(", ", "{", "}"));
306
            logger.debug("resulting representations: " + text);
307
        }
308

  
309
        return prefRepr;
310
    }
311

  
312
    static long expanse(Dimension imageDimension) {
313
        if(imageDimension != null){
314
            return (long)imageDimension.height * (long)imageDimension.width;
315
        } else {
316
            return -1;
317
        }
318
    }
319

  
320
    /**
321
     * @param widthOrDuration
322
     * @param height
323
     * @param mvs Will be applied when both, width and height, are <code>null</code>
324
     */
325
    static Dimension dimensionsFilter(Integer width, Integer height, MissingValueStrategy mvs) {
326
        Dimension imageDimensions = null;
327
        if(!isUndefined(height) || !isUndefined(width)){
328
            imageDimensions = new Dimension();
329
            if (!isUndefined(height) && isUndefined(width)){
330
                imageDimensions.setSize(1, height);
331
            } else if(isUndefined(height) && !isUndefined(width)) {
332
                imageDimensions.setSize(width, 1); // --> height will be respected and width is ignored
333
            } else {
334
                imageDimensions.setSize(width, height);
335
            }
336
        } else {
337
            // both, width and height, are undefined
338

  
339
            if(mvs != null){
340
                // set both values to null so that the MissingValueStrategy can be applied
341
                // the MissingValueStrategy only get effective when the supplied value  is NULL
342
                width = null;
343
                height = null;
344
                imageDimensions = new Dimension(mvs.applyTo(width), mvs.applyTo(height));
345
            }
346
        }
347
        return imageDimensions;
348
    }
349

  
350
    static private boolean isUndefined(Integer val) {
351
        return val == null || val == 0;
352
    }
353

  
354
    /**
355
     * Strategies for replacing <code>null</code> values with a numeric value.
356
     *
357
     * @author a.kohlbecker
358
     */
359
    public enum MissingValueStrategy {
360
        /**
361
         * replace <code>null</code> by {@link Integer#MAX_VALUE}
362
         */
363
        MAX(Integer.MAX_VALUE),
364
        /**
365
         * replace <code>null</code> by <code>0</code>
366
         */
367
        ZERO(0);
368

  
369
        private Integer defaultValue;
370

  
371
        MissingValueStrategy(Integer defaultValue){
372
            this.defaultValue = defaultValue;
373
        }
374

  
375
        public Integer applyTo(Integer val){
376
            if(val == null){
377
                return defaultValue;
378
            } else {
379
                return val;
380
            }
381
        }
382
    }
383
  }
cdmlib-model/src/test/java/eu/etaxonomy/cdm/model/media/MediaUtilsTest.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
 */
1 9
package eu.etaxonomy.cdm.model.media;
2 10

  
3 11
import java.net.URISyntaxException;
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/database/update/v40_50/VocabularyOrderUpdater.java
12 12
import java.util.ArrayList;
13 13
import java.util.List;
14 14

  
15
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;
15
import org.apache.logging.log4j.LogManager;
16
import org.apache.logging.log4j.Logger;
16 17

  
17 18
import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
18 19
import eu.etaxonomy.cdm.database.ICdmDataSource;
......
21 22
import eu.etaxonomy.cdm.database.update.SchemaUpdateResult;
22 23
import eu.etaxonomy.cdm.database.update.SchemaUpdaterStepBase;
23 24

  
24

  
25 25
/**
26 26
 * Updates the CdmPreference NomenclaturalCode  #3658
27 27
 *
......
69 69
	    return;
70 70
	}
71 71

  
72
    /**
73
     * @param datasource
74
     * @param monitor
75
     * @param caseType
76
     * @param result
77
     * @param dat
78
     * @throws SQLException
79
     */
80 72
    private void invokeSingle(ICdmDataSource datasource, IProgressMonitor monitor, CaseType caseType,
81 73
            SchemaUpdateResult result, String[] dat) throws SQLException {
82 74
        String query = "UPDATE DefinedTermBase "
......
85 77
        datasource.executeUpdate(query);
86 78

  
87 79
    }
88

  
89
}
80
}
cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/IClassificationService.java
248 248

  
249 249
    public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(Classification classification);
250 250

  
251
    //FIXME seems not to be used anymore
251 252
    public Map<UUID, List<MediaRepresentation>> getAllMediaForChildNodes(TaxonNode taxonNode, List<String> propertyPaths, int size, int height, int widthOrDuration, String[] mimeTypes);
252 253

  
253 254
    /**

Also available in: Unified diff