Project

General

Profile

Download (28.4 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2020 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.remote.controller.iiif;
10

    
11
import java.io.IOException;
12
import java.net.URI;
13
import java.net.URISyntaxException;
14
import java.util.ArrayList;
15
import java.util.HashMap;
16
import java.util.List;
17
import java.util.Map;
18
import java.util.Objects;
19
import java.util.Optional;
20
import java.util.stream.Collectors;
21

    
22
import org.apache.http.HttpException;
23
import org.apache.log4j.Logger;
24

    
25
import de.digitalcollections.iiif.model.ImageContent;
26
import de.digitalcollections.iiif.model.MetadataEntry;
27
import de.digitalcollections.iiif.model.MimeType;
28
import de.digitalcollections.iiif.model.PropertyValue;
29
import de.digitalcollections.iiif.model.enums.ViewingDirection;
30
import de.digitalcollections.iiif.model.sharedcanvas.Canvas;
31
import de.digitalcollections.iiif.model.sharedcanvas.Manifest;
32
import de.digitalcollections.iiif.model.sharedcanvas.Resource;
33
import de.digitalcollections.iiif.model.sharedcanvas.Sequence;
34
import eu.etaxonomy.cdm.api.service.IMediaService;
35
import eu.etaxonomy.cdm.api.service.l10n.LocaleContext;
36
import eu.etaxonomy.cdm.api.service.media.MediaInfoFactory;
37
import eu.etaxonomy.cdm.common.media.CdmImageInfo;
38
import eu.etaxonomy.cdm.format.reference.OriginalSourceFormatter;
39
import eu.etaxonomy.cdm.model.common.Credit;
40
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
41
import eu.etaxonomy.cdm.model.common.IdentifiableSource;
42
import eu.etaxonomy.cdm.model.common.Identifier;
43
import eu.etaxonomy.cdm.model.common.Language;
44
import eu.etaxonomy.cdm.model.common.LanguageString;
45
import eu.etaxonomy.cdm.model.media.ExternalLink;
46
import eu.etaxonomy.cdm.model.media.ImageFile;
47
import eu.etaxonomy.cdm.model.media.Media;
48
import eu.etaxonomy.cdm.model.media.MediaRepresentation;
49
import eu.etaxonomy.cdm.model.media.MediaRepresentationPart;
50
import eu.etaxonomy.cdm.model.media.MediaUtils;
51
import eu.etaxonomy.cdm.model.media.Rights;
52
import eu.etaxonomy.cdm.model.media.RightsType;
53
import eu.etaxonomy.cdm.model.reference.Reference;
54
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
55
import eu.etaxonomy.cdm.remote.controller.TaxonPortalController.EntityMediaContext;
56
import eu.etaxonomy.cdm.remote.controller.util.IMediaToolbox;
57
import eu.etaxonomy.cdm.strategy.cache.TaggedCacheHelper;
58

    
59
/**
60
 * Factory class for creating iiif manifests.
61
 * <p>
62
 * This class is not state less therefore it is not a spring bean.
63
 *
64
 * @author a.kohlbecker
65
 * @since Aug 18, 2020
66
 */
67
public class ManifestComposer {
68

    
69
    public static final Logger logger = Logger.getLogger(ManifestComposer.class);
70

    
71
    private IMediaToolbox mediaTools;
72

    
73
    private IMediaService mediaService;
74

    
75
    private MediaInfoFactory mediaInfoFactory; // FIXME define and use interface
76

    
77
    private String iiifIdPrefix;
78

    
79
    private String[] thumbnailMimetypes = new String[] {"image/.*", ".*"};
80

    
81

    
82
    private boolean doJoinAttributions = false;
83

    
84
    private boolean useThumbnailDimensionsForCanvas = false;
85

    
86

    
87
    public String getIiifIdPrefix() {
88
        return iiifIdPrefix;
89
    }
90

    
91
    public void setIiifIdPrefix(String iiifIdPrefix) {
92
        this.iiifIdPrefix = iiifIdPrefix;
93
    }
94

    
95

    
96
    public String[] getThumbnailMimetypes() {
97
        return thumbnailMimetypes;
98
    }
99

    
100
    public void setThumbnailMimetypes(String[] thumbnailMimetypes) {
101
        this.thumbnailMimetypes = thumbnailMimetypes;
102
    }
103

    
104
    public boolean isDoJoinAttributions() {
105
        return doJoinAttributions;
106
    }
107

    
108
    /**
109
     * Universal viewer only shows one attribution value in the popup panel
110
     * Therefore it makes sense to join all of them.
111
     */
112
    public void setDoJoinAttributions(boolean doJoinAttributions) {
113
        this.doJoinAttributions = doJoinAttributions;
114
    }
115

    
116
    public boolean isUseThumbnailDimensionsForCanvas() {
117
        return useThumbnailDimensionsForCanvas;
118
    }
119

    
120
    /**
121
     * Width and height of the thumbnail image will be used for the canvas size when this is true.
122
     * Normally the canvas dimensions conform to the image dimension.
123
     * This trick is necessary to achieve a pleasant presentation of the thumbnails in universal viewer,
124
     * see {@linkplain https://dev.e-taxonomy.eu/redmine/issues/9132#note-21} and
125
     *  {@linkplain https://github.com/UniversalViewer/universalviewer/issues/743}
126
     *
127
     */
128
    public void setUseThumbnailDimensionsForCanvas(boolean useThumbnailDimensionsForCanvas) {
129
        this.useThumbnailDimensionsForCanvas = useThumbnailDimensionsForCanvas;
130
    }
131

    
132

    
133
    public ManifestComposer(String iiifIdPrefix, IMediaToolbox mediaTools, IMediaService mediaService, MediaInfoFactory mediaInfoFactory) {
134
        this.mediaTools = mediaTools;
135
        this.iiifIdPrefix = iiifIdPrefix;
136
        this.mediaService = mediaService;
137
        this.mediaInfoFactory = mediaInfoFactory;
138
    }
139

    
140
    <T extends IdentifiableEntity> Manifest manifestFor(EntityMediaContext<T> entityMediaContext, String onEntitiyType, String onEntityUuid) throws IOException {
141

    
142
//        Logger.getLogger(MediaUtils.class).setLevel(Level.DEBUG);
143
//        logger.setLevel(Level.DEBUG);
144
        List<Canvas> canvases = null;
145
        try {
146
        canvases = entityMediaContext.getMedia().parallelStream().map(m -> {
147
                try {
148
                    return createCanvas(onEntitiyType, onEntityUuid, m);
149
                } catch (IOException e) {
150
                    throw new RuntimeException(e);
151
                }
152
            })
153
            .filter(Optional::isPresent)
154
            .map(Optional::get)
155
            .collect(Collectors.toList());
156
        } catch(RuntimeException re) {
157
            // re-throw  IOException from lambda expression
158
            throw new IOException(re.getCause());
159
        }
160

    
161
        Sequence sequence = null;
162
        if(canvases.size() > 0) {
163
            sequence = new Sequence(iiifID(onEntitiyType, onEntityUuid, Sequence.class, "default"));
164
            sequence.setViewingDirection(ViewingDirection.LEFT_TO_RIGHT);
165
            sequence.setCanvases(canvases);
166
            sequence.setStartCanvas(canvases.get(0).getIdentifier());
167
        }
168

    
169
        Manifest manifest = new Manifest(iiifID(onEntitiyType, onEntityUuid, Manifest.class, null));
170
        if(sequence != null){
171
            // manifest.setLabel(new PropertyValue("Media for " + onEntitiyType + "[" + onEntityUuid + "]")); // TODO better label!!
172
            manifest.addSequence(sequence);
173
        } else {
174
            manifest.setLabel(new PropertyValue("No media found for " + onEntitiyType + "[" + onEntityUuid + "]")); // TODO better label!!
175
        }
176
        List<MetadataEntry> entityMetadata = entityMetadata(entityMediaContext.getEntity());
177
        manifest.addMetadata(entityMetadata.toArray(new MetadataEntry[entityMetadata.size()]));
178
        copyAttributionAndLicenseToManifest(manifest);
179

    
180
        return manifest;
181
    }
182

    
183
    public Optional<Canvas> createCanvas(String onEntitiyType, String onEntityUuid, Media media) throws IOException {
184
        Canvas canvas;
185

    
186
        MediaRepresentation thumbnailRepresentation = mediaTools.processAndFindBestMatchingRepresentation(media, null, null, 100, 100, thumbnailMimetypes, MediaUtils.MissingValueStrategy.MAX);
187
        MediaRepresentation fullSizeRepresentation = mediaTools.processAndFindBestMatchingRepresentation(media, null, null, Integer.MAX_VALUE, Integer.MAX_VALUE, null, MediaUtils.MissingValueStrategy.MAX);
188
        // MediaRepresentation fullSizeRepresentation = MediaUtils.findBestMatchingRepresentation(media, null, null, Integer.MAX_VALUE, Integer.MAX_VALUE, null, MediaUtils.MissingValueStrategy.MAX);
189
        // MediaRepresentation thumbnailRepresentation = MediaUtils.findBestMatchingRepresentation(media, null, null, 100, 100, tumbnailMimetypes, MediaUtils.MissingValueStrategy.MAX);
190
        if(logger.isDebugEnabled()){
191
            logger.debug("fullSizeRepresentation: " + fullSizeRepresentation.getParts().get(0).getUri());
192
            logger.debug("thumbnailRepresentation: " + thumbnailRepresentation.getParts().get(0).getUri());
193
        }
194

    
195
        List<ImageContent> thumbnailImageContents;
196
        List<ImageContent> fullSizeImageContents;
197
        // FIXME the below only makes sense if the media is an Image!!!!!
198
        if(fullSizeRepresentation != null) {
199
            fullSizeImageContents = representationPartsToImageContent(fullSizeRepresentation);
200
        } else {
201
            fullSizeImageContents = new ArrayList<>(0);
202
        }
203

    
204
        if(Objects.equals(fullSizeRepresentation, thumbnailRepresentation)){
205
            thumbnailImageContents = fullSizeImageContents;
206
        } else {
207
            thumbnailImageContents = representationPartsToImageContent(thumbnailRepresentation);
208
        }
209

    
210
        if(fullSizeRepresentation == null) {
211
            return Optional.empty();
212
        }
213

    
214
        canvas = new Canvas(iiifID(onEntitiyType, onEntityUuid, Canvas.class, "media-" + media.getUuid()));
215
        for(Language lang : media.getAllTitles().keySet()){
216
            LanguageString titleLocalized = media.getAllTitles().get(lang);
217
            canvas.addLabel(titleLocalized.getText());
218
        }
219
        canvas.setLabel(new PropertyValue(media.getTitleCache()));
220
        canvas.setThumbnails(thumbnailImageContents);
221
        for(ImageContent image  : fullSizeImageContents){
222
            canvas.addImage(image);
223
        }
224
        // TODO  if there is only one image canvas.addImage() internally sets the canvas width and height
225
        //      to the height of the image, for multiple images it is required to follow the specification:
226
        //
227
        // IIIF Presentation API 2.1.1:
228
        // It is recommended that if there is (at the time of implementation) a single image that depicts the page,
229
        // then the dimensions of the image are used as the dimensions of the canvas for simplicity. If there are
230
        // multiple full images, then the dimensions of the largest image should be used. If the largest image’s
231
        // dimensions are less than 1200 pixels on either edge, then the canvas’s dimensions should be double those
232
        // of the image.
233

    
234
        // apply hack for accurate thumbnail container aspect ratios see setUseThumbnailDimensionsForCanvas() for an
235
        // explanation
236
        if(useThumbnailDimensionsForCanvas && !thumbnailImageContents.isEmpty()) {
237
            if(thumbnailImageContents.get(0).getHeight() != null && thumbnailImageContents.get(0).getHeight() > 0 && thumbnailImageContents.get(0).getWidth() != null && thumbnailImageContents.get(0).getWidth() > 0) {
238
                canvas.setHeight(thumbnailImageContents.get(0).getHeight());
239
                canvas.setWidth(thumbnailImageContents.get(0).getWidth());
240
            }
241
        }
242

    
243
        List<MetadataEntry> mediaMetadata = mediaMetaData(media);
244
        List<MetadataEntry> representationMetadata;
245
        try {
246
            representationMetadata = mediaService.readResourceMetadataFiltered(fullSizeRepresentation)
247
                     .entrySet()
248
                     .stream()
249
                     .map(e -> new MetadataEntry(e.getKey(), e.getValue())).collect(Collectors.toList());
250
            mediaMetadata.addAll(representationMetadata);
251
        } catch (IOException e) {
252
            logger.error("Error reading media metadata", e);
253
        } catch (HttpException e) {
254
            logger.error("Error accessing remote media resource", e);
255
        }
256

    
257
        // extractAndAddDesciptions(canvas, mediaMetadata);
258
        mediaMetadata = deduplicateMetadata(mediaMetadata);
259
        canvas = addAttributionAndLicense(media, canvas, mediaMetadata);
260
        orderMedatadaItems(canvas);
261
        canvas.addMetadata(mediaMetadata.toArray(new MetadataEntry[mediaMetadata.size()]));
262
        return Optional.of(canvas);
263
    }
264

    
265
    /**
266
     * Due to limitations in universal viewer it seems not to be able
267
     * to show attribution and licenses, therefore we copy this data to
268
     * also to the metadata
269
     *
270
     * <b>NOTE:</b> This method expects that the canvas attributions and
271
     * licenses are not localized!!!!
272
     *
273
     * @param canvas
274
     */
275
    private void copyAttributionAndLicenseToManifest(Manifest manifest) {
276

    
277
         PropertyValue attributions = new PropertyValue();
278
         List<URI> licenses = new ArrayList<>();
279
         String firstAttributionString = null;
280
         boolean hasAttributions = false;
281
         boolean hasLicenses = false;
282
         boolean hasDiversAttributions = false;
283
         boolean hasDiversLicenses = false;
284
         String firstLicensesString = null;
285

    
286
         if(manifest.getSequences() == null){
287
             // nothing to do, skip!
288
             return;
289
         }
290

    
291
        for (Sequence sequence : manifest.getSequences()) {
292
            for (Canvas canvas : sequence.getCanvases()) {
293
                if (canvas.getAttribution() != null) {
294
                    canvas.getAttribution().getValues().stream().forEachOrdered(val -> attributions.addValue(val));
295
                    String thisAttributionString = canvas.getAttribution().getValues()
296
                                .stream()
297
                                .sorted()
298
                                .collect(Collectors.joining());
299
                    if(firstAttributionString == null){
300
                        firstAttributionString = thisAttributionString;
301
                        hasAttributions = true;
302
                    } else {
303
                        hasDiversAttributions |=  !firstAttributionString.equals(thisAttributionString);
304
                    }
305
                }
306
                if (canvas.getLicenses() != null && canvas.getLicenses().size() > 0) {
307
                    licenses.addAll(canvas.getLicenses());
308
                    String thisLicensesString = canvas.getLicenses()
309
                                .stream()
310
                                .map(URI::toString)
311
                                .sorted()
312
                                .collect(Collectors.joining());
313
                    if(firstLicensesString == null){
314
                        firstLicensesString = thisLicensesString;
315
                        hasLicenses = true;
316
                    } else {
317
                        hasDiversLicenses |=  !firstLicensesString.equals(thisLicensesString);
318
                    }
319
                }
320
            }
321
        }
322
        String diversityInfo = "";
323

    
324
        if(hasAttributions || hasLicenses){
325
            String dataTypes ;
326
            if(hasAttributions && hasLicenses) {
327
                dataTypes = "attributions and licenses";
328
            } else if(hasAttributions){
329
                dataTypes = "attributions";
330
            } else {
331
                dataTypes = "licenses";
332
            }
333
            if(hasDiversAttributions || hasDiversLicenses){
334
                diversityInfo = "Individual " + dataTypes + " per Item:";
335
            } else {
336
                diversityInfo = "Same " + dataTypes + " for any Item:";
337
            }
338
            if(hasAttributions){
339
                List<String> attrs = new ArrayList<>(attributions.getValues());
340
                attrs = attrs.stream().sorted().distinct().collect(Collectors.toList());
341
                if(doJoinAttributions){
342
                    attrs.add(0, diversityInfo + "<br/>" + attrs.get(0));
343
                    attrs.remove(1);
344
                    manifest.addAttribution(attrs.stream()
345
                            .sorted()
346
                            .distinct()
347
                            .collect(Collectors.joining("; ")));
348
                } else {
349
                    manifest.addAttribution(diversityInfo, attrs.toArray(
350
                            new String[attributions.getValues().size()]
351
                            ));
352
                }
353
            }
354
            licenses.stream()
355
                .map(URI::toString)
356
                .sorted()
357
                .distinct()
358
                .forEachOrdered(l -> manifest.addLicense(l));
359
        }
360
    }
361

    
362
    private void orderMedatadaItems(Canvas canvas) {
363
        // TODO Auto-generated method stub
364
        // order by label name, Title, description, author, license, attribution should come first.
365
    }
366

    
367
    private List<MetadataEntry> deduplicateMetadata(List<MetadataEntry> mediaMetadata) {
368
        Map<String, MetadataEntry> dedupMap = new HashMap<>();
369
        mediaMetadata.stream().forEach(mde -> {
370
                String dedupKey = mde.getLabelString() + ":" + mde.getValueString();
371
                dedupMap.put(dedupKey, mde);
372
            }
373
        );
374
        return new ArrayList<>(dedupMap.values());
375
    }
376

    
377
    private void extractAndAddDesciptions(Resource resource, List<MetadataEntry> mediaMetadata) {
378
        List<MetadataEntry> descriptions = mediaMetadata.stream()
379
            .filter(mde -> mde.getLabelString().toLowerCase().matches(".*description.*|.*caption.*"))
380
            .collect(Collectors.toList());
381
        mediaMetadata.removeAll(descriptions);
382
        // FIXME deduplicate mde.getValueString()
383
        // descriptions.sream ...
384
        descriptions.stream().forEach(mde -> resource.addDescription(mde.getValueString()));
385
    }
386

    
387
    private <T extends IdentifiableEntity> List<MetadataEntry> entityMetadata(T entity) {
388

    
389
        List<MetadataEntry> metadata = new ArrayList<>();
390
        if(entity instanceof TaxonBase){
391
            List taggedTitle = ((TaxonBase)entity).getTaggedTitle();
392
            if(taggedTitle != null){
393
                //FIXME taggedTitel to HTML!!!!
394
                metadata.add(new MetadataEntry(entity.getClass().getSimpleName(), TaggedCacheHelper.createString(taggedTitle)));
395
            }
396
        } else {
397
            String titleCache = entity.getTitleCache();
398
            if(titleCache != null){
399
                metadata.add(new MetadataEntry(entity.getClass().getSimpleName(), titleCache));
400
            }
401
        }
402

    
403
        return metadata;
404
    }
405

    
406
    /**
407
     * @deprecated unused as media metadata is now read via the mediaService,
408
     *  see {@link IMediaService#readResourceMetadataFiltered(MediaRepresentation)}
409
     */
410
    @Deprecated
411
    private List<MetadataEntry> mediaRepresentationMetaData(MediaRepresentation representation) {
412

    
413
        List<MetadataEntry> metadata = new ArrayList<>();
414
        boolean needsPrefix = representation.getParts().size() > 1;
415
        int partIndex = 1;
416

    
417
        for (MediaRepresentationPart part : representation.getParts()) {
418
            String prefix = "";
419
            if (needsPrefix) {
420
                prefix = "Part" + partIndex + " ";
421
            }
422
            if (part.getUri() != null) {
423
                try {
424
                    CdmImageInfo cdmImageInfo = mediaInfoFactory.cdmImageInfoWithMetaData(part.getUri());
425
                    Map<String, String> result = cdmImageInfo.getMetaData();
426
                    if(result != null){
427
                        for (String key : result.keySet()) {
428
                            metadata.add(new MetadataEntry(key, result.get(key)));
429
                        }
430
                    }
431
                } catch (IOException | HttpException e) {
432
                    logger.error("Problem while loading image metadata", e);
433
                    metadata.add(new MetadataEntry(prefix + " Error:", "Problem while loading image metadata <br/><small>(" + e.getLocalizedMessage() + ")</small>"));
434
                }
435
            }
436
        }
437

    
438
        return metadata;
439
  }
440

    
441
    private List<MetadataEntry> mediaMetaData(Media media) {
442
        List<MetadataEntry> metadata = new ArrayList<>();
443
        List<Language> languages = LocaleContext.getLanguages();
444

    
445

    
446
        if(media.getTitle() != null){
447
            // TODO get localized titleCache
448
            metadata.add(new MetadataEntry("Title", media.getTitleCache()));
449
        }
450
        if(media.getArtist() != null){
451
            metadata.add(new MetadataEntry("Artist", media.getArtist().getTitleCache()));
452
        }
453
        if(media.getAllDescriptions().size() > 0){
454
            // TODO get localized description
455
            PropertyValue descriptionValues = new PropertyValue();
456
            for(LanguageString description : media.getAllDescriptions().values()){
457
                descriptionValues.addValue(description.getText());
458
            }
459
            metadata.add(new MetadataEntry(new PropertyValue("Description"), descriptionValues));
460
        }
461
        if(media.getMediaCreated() != null){
462
            metadata.add(new MetadataEntry("Created on", media.getMediaCreated().toString())); // TODO is this correct to string conversion?
463
        }
464

    
465
        if(!media.getIdentifiers().isEmpty()) {
466
            PropertyValue identifierValues = new PropertyValue();
467
            for(Identifier identifier : media.getIdentifiers()){
468
                if(identifier.getIdentifier() != null) {
469
                    identifierValues.addValue(identifier.getIdentifier());
470
                }
471
            }
472
            metadata.add(new MetadataEntry(new PropertyValue("Identifiers"), identifierValues));
473
        }
474

    
475
        if(!media.getSources().isEmpty()) {
476
            PropertyValue descriptionValues = new PropertyValue();
477
            for(IdentifiableSource source : media.getSources()){
478
                descriptionValues.addValue(sourceAsHtml(source));
479
            }
480
            metadata.add(new MetadataEntry(new PropertyValue("Sources"), descriptionValues));
481
        }
482
        return metadata;
483
    }
484

    
485
    private String sourceAsHtml(IdentifiableSource source) {
486

    
487
        StringBuilder html = new StringBuilder();
488
        Reference citation = source.getCitation();
489
        if(citation != null) {
490
            html.append(OriginalSourceFormatter.INSTANCE.format(source)).append(" ");
491
            if(citation.getDoi() != null) {
492
                try {
493
                    html.append(" ").append(htmlLink(new URI("http://doi.org/" + citation.getDoiString()), citation.getDoiString()));
494
                } catch (URISyntaxException e) {
495
                    // IGNORE, should never happen
496
                }
497
            }
498
        }
499
        if(source.getIdNamespace() != null && source.getIdInSource() != null) {
500
            html.append(source.getIdNamespace()).append("/").append(source.getIdInSource()).append(" ");
501
        }
502

    
503
        String linkhtml = null;
504
        for(ExternalLink extLink : source.getLinks()) {
505
            if(extLink.getUri() != null) {
506
                if(linkhtml != null) {
507
                    html.append(", ");
508
                }
509
                linkhtml = htmlLink(extLink.getUri().getJavaUri(), extLink.getUri().toString());
510
                html.append(linkhtml);
511
            }
512
        }
513
        return html.toString();
514
    }
515

    
516
    private <T extends Resource<T>> T addAttributionAndLicense(IdentifiableEntity<?> entity, T resource, List<MetadataEntry> metadata) {
517

    
518
        List<Language> languages = LocaleContext.getLanguages();
519

    
520
        List<String> rightsTexts = new ArrayList<>();
521
        List<String> creditTexts = new ArrayList<>();
522
        List<URI> license = new ArrayList<>();
523

    
524
        if(entity.getRights() != null && entity.getRights().size() > 0){
525
            for(Rights right : entity.getRights()){
526
                String rightText = "";
527
                // TODO get localized texts below
528
                // --- LICENSE or NULL
529
                if(right.getType() == null || right.getType().equals(RightsType.LICENSE())){
530
                    String licenseText = "";
531
                    String licenseAbbrev = "";
532
                    if(right.getText() != null){
533
                        licenseText = right.getText();
534
                    }
535
                    if(right.getAbbreviatedText() != null){
536
                        licenseAbbrev = right.getAbbreviatedText().trim();
537
                    }
538
                    if(right.getUri() != null){
539
                        if(!licenseAbbrev.isEmpty()) {
540
                            licenseAbbrev =  htmlLink(right.getUri().getJavaUri(), licenseAbbrev);
541
                        } else if(!licenseText.isEmpty()) {
542
                            licenseText =  htmlLink(right.getUri().getJavaUri(), licenseText);
543
                        } else {
544
                            licenseText =  htmlLink(right.getUri().getJavaUri(), right.getUri().toString());
545
                        }
546
                        license.add(right.getUri().getJavaUri());
547
                    }
548
                    rightText = licenseAbbrev + (licenseText.isEmpty() ? "" : " ") + licenseText;
549
                }
550
                // --- COPYRIGHT
551
                else if(right.getType().equals(RightsType.COPYRIGHT())){
552
                    // titleCache + agent
553
                    String copyRightText = "";
554
                    if(right.getText() != null){
555
                        copyRightText = right.getText();
556
                        //  sanitize potential '(c)' away
557
                        copyRightText = copyRightText.replace("(c)", "").trim();
558
                    }
559
                    if(right.getAgent() != null){
560
                        // may only apply to RightsType.accessRights
561
                        copyRightText += " " + right.getAgent().getTitleCache();
562
                    }
563
                    if(!copyRightText.isEmpty()){
564
                        copyRightText = "© " + copyRightText;
565
                    }
566
                    rightText = copyRightText;
567
                } else
568
                if(right.getType().equals(RightsType.ACCESS_RIGHTS())){
569
                    // titleCache + agent
570
                    String accessRights = right.getText();
571
                    if(right.getAgent() != null){
572
                        // may only apply to RightsType.accessRights
573
                        accessRights = " " + right.getAgent().getTitleCache();
574
                    }
575
                    rightText = accessRights;
576
                }
577
                if(!rightText.isEmpty()){
578
                    rightsTexts.add(rightText);
579
                }
580
            }
581
        }
582
        if(entity.getCredits() != null && entity.getCredits().size() > 0){
583
            for(Credit credit : entity.getCredits()){
584
                String creditText = "";
585
                if(credit.getText() != null){
586
                    creditText += credit.getText();
587
                }
588
                if(creditText.isEmpty() && credit.getAbbreviatedText() != null){
589
                    creditText += credit.getAbbreviatedText();
590
                }
591
                if(credit.getAgent() != null){
592
                    // may only apply to RightsType.accessRights
593
                    creditText += " " + credit.getAgent().getTitleCache();
594
                }
595
                creditTexts.add(creditText);
596
            }
597
        }
598

    
599
        if(rightsTexts.size() > 0){
600
            String joinedRights = rightsTexts.stream().collect(Collectors.joining(", "));
601
            resource.addAttribution(joinedRights);
602
            if(metadata != null){
603
                metadata.add(new MetadataEntry(new PropertyValue("Copyright"), new PropertyValue(joinedRights)));
604
            }
605
        }
606
        if(creditTexts.size() > 0){
607
            String joinedCredits = creditTexts.stream().collect(Collectors.joining(", "));
608
            resource.addAttribution(joinedCredits);
609
            if(metadata != null){
610
                metadata.add(new MetadataEntry(new PropertyValue("Credit"), new PropertyValue(joinedCredits)));
611
            }
612
        }
613
        resource.setLicenses(license);
614
        return resource;
615
    }
616

    
617
    private String htmlLink(URI uri, String text) {
618
        return String.format(" <a href=\"%s\">%s</a>", uri, text);
619
    }
620

    
621
    private List<ImageContent> representationPartsToImageContent(MediaRepresentation representation) {
622
        List<ImageContent> imageContents = new ArrayList<>();
623
        for(MediaRepresentationPart part : representation.getParts()){
624
            if(part.getUri() != null){
625
                ImageContent ic = new ImageContent(part.getUri().toString());
626
                if(part instanceof ImageFile){
627
                    ImageFile image = (ImageFile)part;
628
                    if(image.getWidth() != null && image.getWidth() > 0){
629
                        ic.setWidth(image.getWidth());
630
                    }
631
                    if(image.getHeight() != null && image.getHeight() > 0){
632
                        ic.setHeight(image.getHeight());
633
                    }
634
                    if(representation.getMimeType() != null){
635
                        ic.setFormat(MimeType.fromTypename(representation.getMimeType()));
636
                    } else {
637
                        ic.setFormat(MimeType.MIME_IMAGE);
638
                    }
639
                }
640
                imageContents.add(ic);
641
            }
642
        }
643
        return imageContents;
644
    }
645

    
646
    private String iiifID(String onEntitiyType, String onEntityUuid, Class<? extends Resource> iiifType, Object index) {
647
        String indexPart = "";
648
        if(index != null){
649
            indexPart = "/" + index.toString();
650
        }
651
        return this.iiifIdPrefix + onEntitiyType + "/" + onEntityUuid + "/" + iiifType.getSimpleName().toLowerCase() + indexPart;
652
    }
653

    
654
}
(1-1/2)