2 * Copyright (C) 2020 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
9 package eu
.etaxonomy
.cdm
.remote
.controller
.iiif
;
11 import java
.io
.IOException
;
13 import java
.net
.URISyntaxException
;
14 import java
.util
.ArrayList
;
15 import java
.util
.HashMap
;
16 import java
.util
.List
;
18 import java
.util
.Objects
;
19 import java
.util
.Optional
;
20 import java
.util
.stream
.Collectors
;
22 import org
.apache
.http
.HttpException
;
23 import org
.apache
.logging
.log4j
.LogManager
;
24 import org
.apache
.logging
.log4j
.Logger
;
26 import de
.digitalcollections
.iiif
.model
.ImageContent
;
27 import de
.digitalcollections
.iiif
.model
.MetadataEntry
;
28 import de
.digitalcollections
.iiif
.model
.MimeType
;
29 import de
.digitalcollections
.iiif
.model
.PropertyValue
;
30 import de
.digitalcollections
.iiif
.model
.enums
.ViewingDirection
;
31 import de
.digitalcollections
.iiif
.model
.sharedcanvas
.Canvas
;
32 import de
.digitalcollections
.iiif
.model
.sharedcanvas
.Manifest
;
33 import de
.digitalcollections
.iiif
.model
.sharedcanvas
.Resource
;
34 import de
.digitalcollections
.iiif
.model
.sharedcanvas
.Sequence
;
35 import eu
.etaxonomy
.cdm
.api
.service
.IMediaService
;
36 import eu
.etaxonomy
.cdm
.api
.service
.l10n
.LocaleContext
;
37 import eu
.etaxonomy
.cdm
.api
.service
.media
.MediaInfoFactory
;
38 import eu
.etaxonomy
.cdm
.common
.media
.CdmImageInfo
;
39 import eu
.etaxonomy
.cdm
.format
.reference
.OriginalSourceFormatter
;
40 import eu
.etaxonomy
.cdm
.model
.common
.Credit
;
41 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
42 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableSource
;
43 import eu
.etaxonomy
.cdm
.model
.common
.Identifier
;
44 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
45 import eu
.etaxonomy
.cdm
.model
.common
.LanguageString
;
46 import eu
.etaxonomy
.cdm
.model
.media
.ExternalLink
;
47 import eu
.etaxonomy
.cdm
.model
.media
.ImageFile
;
48 import eu
.etaxonomy
.cdm
.model
.media
.Media
;
49 import eu
.etaxonomy
.cdm
.model
.media
.MediaRepresentation
;
50 import eu
.etaxonomy
.cdm
.model
.media
.MediaRepresentationPart
;
51 import eu
.etaxonomy
.cdm
.model
.media
.MediaUtils
;
52 import eu
.etaxonomy
.cdm
.model
.media
.Rights
;
53 import eu
.etaxonomy
.cdm
.model
.media
.RightsType
;
54 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
55 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
56 import eu
.etaxonomy
.cdm
.remote
.controller
.TaxonPortalController
.EntityMediaContext
;
57 import eu
.etaxonomy
.cdm
.remote
.controller
.util
.IMediaToolbox
;
58 import eu
.etaxonomy
.cdm
.strategy
.cache
.TaggedCacheHelper
;
61 * Factory class for creating iiif manifests.
63 * This class is not state less therefore it is not a spring bean.
65 * @author a.kohlbecker
68 public class ManifestComposer
{
70 private static final Logger logger
= LogManager
.getLogger();
72 private IMediaToolbox mediaTools
;
74 private IMediaService mediaService
;
76 private MediaInfoFactory mediaInfoFactory
; // FIXME define and use interface
78 private String iiifIdPrefix
;
80 private String
[] thumbnailMimetypes
= new String
[] {"image/.*", ".*"};
83 private boolean doJoinAttributions
= false;
85 private boolean useThumbnailDimensionsForCanvas
= false;
88 public String
getIiifIdPrefix() {
92 public void setIiifIdPrefix(String iiifIdPrefix
) {
93 this.iiifIdPrefix
= iiifIdPrefix
;
97 public String
[] getThumbnailMimetypes() {
98 return thumbnailMimetypes
;
101 public void setThumbnailMimetypes(String
[] thumbnailMimetypes
) {
102 this.thumbnailMimetypes
= thumbnailMimetypes
;
105 public boolean isDoJoinAttributions() {
106 return doJoinAttributions
;
110 * Universal viewer only shows one attribution value in the popup panel
111 * Therefore it makes sense to join all of them.
113 public void setDoJoinAttributions(boolean doJoinAttributions
) {
114 this.doJoinAttributions
= doJoinAttributions
;
117 public boolean isUseThumbnailDimensionsForCanvas() {
118 return useThumbnailDimensionsForCanvas
;
122 * Width and height of the thumbnail image will be used for the canvas size when this is true.
123 * Normally the canvas dimensions conform to the image dimension.
124 * This trick is necessary to achieve a pleasant presentation of the thumbnails in universal viewer,
125 * see {@linkplain https://dev.e-taxonomy.eu/redmine/issues/9132#note-21} and
126 * {@linkplain https://github.com/UniversalViewer/universalviewer/issues/743}
129 public void setUseThumbnailDimensionsForCanvas(boolean useThumbnailDimensionsForCanvas
) {
130 this.useThumbnailDimensionsForCanvas
= useThumbnailDimensionsForCanvas
;
134 public ManifestComposer(String iiifIdPrefix
, IMediaToolbox mediaTools
, IMediaService mediaService
, MediaInfoFactory mediaInfoFactory
) {
135 this.mediaTools
= mediaTools
;
136 this.iiifIdPrefix
= iiifIdPrefix
;
137 this.mediaService
= mediaService
;
138 this.mediaInfoFactory
= mediaInfoFactory
;
141 <T
extends IdentifiableEntity
> Manifest
manifestFor(EntityMediaContext
<T
> entityMediaContext
, String onEntitiyType
, String onEntityUuid
) throws IOException
{
143 // LogUtils.setLevel(MediaUtils.class, Level.DEBUG);
144 // LogUtils.setLevel(logger, Level.DEBUG);
145 List
<Canvas
> canvases
= null;
147 canvases
= entityMediaContext
.getMedia().parallelStream().map(m
-> {
149 return createCanvas(onEntitiyType
, onEntityUuid
, m
);
150 } catch (IOException e
) {
151 throw new RuntimeException(e
);
154 .filter(Optional
::isPresent
)
156 .collect(Collectors
.toList());
157 } catch(RuntimeException re
) {
158 // re-throw IOException from lambda expression
159 throw new IOException(re
.getCause());
162 Sequence sequence
= null;
163 if(canvases
.size() > 0) {
164 sequence
= new Sequence(iiifID(onEntitiyType
, onEntityUuid
, Sequence
.class, "default"));
165 sequence
.setViewingDirection(ViewingDirection
.LEFT_TO_RIGHT
);
166 sequence
.setCanvases(canvases
);
167 sequence
.setStartCanvas(canvases
.get(0).getIdentifier());
170 Manifest manifest
= new Manifest(iiifID(onEntitiyType
, onEntityUuid
, Manifest
.class, null));
171 if(sequence
!= null){
172 // manifest.setLabel(new PropertyValue("Media for " + onEntitiyType + "[" + onEntityUuid + "]")); // TODO better label!!
173 manifest
.addSequence(sequence
);
175 manifest
.setLabel(new PropertyValue("No media found for " + onEntitiyType
+ "[" + onEntityUuid
+ "]")); // TODO better label!!
177 List
<MetadataEntry
> entityMetadata
= entityMetadata(entityMediaContext
.getEntity());
178 manifest
.addMetadata(entityMetadata
.toArray(new MetadataEntry
[entityMetadata
.size()]));
179 copyAttributionAndLicenseToManifest(manifest
);
184 public Optional
<Canvas
> createCanvas(String onEntitiyType
, String onEntityUuid
, Media media
) throws IOException
{
187 MediaRepresentation thumbnailRepresentation
= mediaTools
.processAndFindBestMatchingRepresentation(media
, null, null, 100, 100, thumbnailMimetypes
, MediaUtils
.MissingValueStrategy
.MAX
);
188 MediaRepresentation fullSizeRepresentation
= mediaTools
.processAndFindBestMatchingRepresentation(media
, null, null, Integer
.MAX_VALUE
, Integer
.MAX_VALUE
, null, MediaUtils
.MissingValueStrategy
.MAX
);
189 // MediaRepresentation fullSizeRepresentation = MediaUtils.findBestMatchingRepresentation(media, null, null, Integer.MAX_VALUE, Integer.MAX_VALUE, null, MediaUtils.MissingValueStrategy.MAX);
190 // MediaRepresentation thumbnailRepresentation = MediaUtils.findBestMatchingRepresentation(media, null, null, 100, 100, tumbnailMimetypes, MediaUtils.MissingValueStrategy.MAX);
191 if(logger
.isDebugEnabled()){
192 logger
.debug("fullSizeRepresentation: " + fullSizeRepresentation
.getParts().get(0).getUri());
193 logger
.debug("thumbnailRepresentation: " + thumbnailRepresentation
.getParts().get(0).getUri());
196 List
<ImageContent
> thumbnailImageContents
;
197 List
<ImageContent
> fullSizeImageContents
;
198 // FIXME the below only makes sense if the media is an Image!!!!!
199 if(fullSizeRepresentation
!= null) {
200 fullSizeImageContents
= representationPartsToImageContent(fullSizeRepresentation
);
202 fullSizeImageContents
= new ArrayList
<>(0);
205 if(Objects
.equals(fullSizeRepresentation
, thumbnailRepresentation
)){
206 thumbnailImageContents
= fullSizeImageContents
;
208 thumbnailImageContents
= representationPartsToImageContent(thumbnailRepresentation
);
211 if(fullSizeRepresentation
== null) {
212 return Optional
.empty();
215 canvas
= new Canvas(iiifID(onEntitiyType
, onEntityUuid
, Canvas
.class, "media-" + media
.getUuid()));
216 for(Language lang
: media
.getAllTitles().keySet()){
217 LanguageString titleLocalized
= media
.getAllTitles().get(lang
);
218 canvas
.addLabel(titleLocalized
.getText());
220 canvas
.setLabel(new PropertyValue(media
.getTitleCache()));
221 canvas
.setThumbnails(thumbnailImageContents
);
222 for(ImageContent image
: fullSizeImageContents
){
223 canvas
.addImage(image
);
225 // TODO if there is only one image canvas.addImage() internally sets the canvas width and height
226 // to the height of the image, for multiple images it is required to follow the specification:
228 // IIIF Presentation API 2.1.1:
229 // It is recommended that if there is (at the time of implementation) a single image that depicts the page,
230 // then the dimensions of the image are used as the dimensions of the canvas for simplicity. If there are
231 // multiple full images, then the dimensions of the largest image should be used. If the largest image’s
232 // dimensions are less than 1200 pixels on either edge, then the canvas’s dimensions should be double those
235 // apply hack for accurate thumbnail container aspect ratios see setUseThumbnailDimensionsForCanvas() for an
237 if(useThumbnailDimensionsForCanvas
&& !thumbnailImageContents
.isEmpty()) {
238 if(thumbnailImageContents
.get(0).getHeight() != null && thumbnailImageContents
.get(0).getHeight() > 0 && thumbnailImageContents
.get(0).getWidth() != null && thumbnailImageContents
.get(0).getWidth() > 0) {
239 canvas
.setHeight(thumbnailImageContents
.get(0).getHeight());
240 canvas
.setWidth(thumbnailImageContents
.get(0).getWidth());
244 List
<MetadataEntry
> mediaMetadata
= mediaMetaData(media
);
245 List
<MetadataEntry
> representationMetadata
;
247 representationMetadata
= mediaService
.readResourceMetadataFiltered(fullSizeRepresentation
)
250 .map(e
-> new MetadataEntry(e
.getKey(), e
.getValue())).collect(Collectors
.toList());
251 mediaMetadata
.addAll(representationMetadata
);
252 } catch (IOException e
) {
253 logger
.error("Error reading media metadata", e
);
254 } catch (HttpException e
) {
255 logger
.error("Error accessing remote media resource", e
);
258 // extractAndAddDesciptions(canvas, mediaMetadata);
259 mediaMetadata
= deduplicateMetadata(mediaMetadata
);
260 canvas
= addAttributionAndLicense(media
, canvas
, mediaMetadata
);
261 orderMedatadaItems(canvas
);
262 canvas
.addMetadata(mediaMetadata
.toArray(new MetadataEntry
[mediaMetadata
.size()]));
263 return Optional
.of(canvas
);
267 * Due to limitations in universal viewer it seems not to be able
268 * to show attribution and licenses, therefore we copy this data to
269 * also to the metadata
271 * <b>NOTE:</b> This method expects that the canvas attributions and
272 * licenses are not localized!!!!
276 private void copyAttributionAndLicenseToManifest(Manifest manifest
) {
278 PropertyValue attributions
= new PropertyValue();
279 List
<URI
> licenses
= new ArrayList
<>();
280 String firstAttributionString
= null;
281 boolean hasAttributions
= false;
282 boolean hasLicenses
= false;
283 boolean hasDiversAttributions
= false;
284 boolean hasDiversLicenses
= false;
285 String firstLicensesString
= null;
287 if(manifest
.getSequences() == null){
288 // nothing to do, skip!
292 for (Sequence sequence
: manifest
.getSequences()) {
293 for (Canvas canvas
: sequence
.getCanvases()) {
294 if (canvas
.getAttribution() != null) {
295 canvas
.getAttribution().getValues().stream().forEachOrdered(val
-> attributions
.addValue(val
));
296 String thisAttributionString
= canvas
.getAttribution().getValues()
299 .collect(Collectors
.joining());
300 if(firstAttributionString
== null){
301 firstAttributionString
= thisAttributionString
;
302 hasAttributions
= true;
304 hasDiversAttributions
|= !firstAttributionString
.equals(thisAttributionString
);
307 if (canvas
.getLicenses() != null && canvas
.getLicenses().size() > 0) {
308 licenses
.addAll(canvas
.getLicenses());
309 String thisLicensesString
= canvas
.getLicenses()
313 .collect(Collectors
.joining());
314 if(firstLicensesString
== null){
315 firstLicensesString
= thisLicensesString
;
318 hasDiversLicenses
|= !firstLicensesString
.equals(thisLicensesString
);
323 String diversityInfo
= "";
325 if(hasAttributions
|| hasLicenses
){
327 if(hasAttributions
&& hasLicenses
) {
328 dataTypes
= "attributions and licenses";
329 } else if(hasAttributions
){
330 dataTypes
= "attributions";
332 dataTypes
= "licenses";
334 if(hasDiversAttributions
|| hasDiversLicenses
){
335 diversityInfo
= "Individual " + dataTypes
+ " per Item:";
337 diversityInfo
= "Same " + dataTypes
+ " for any Item:";
340 List
<String
> attrs
= new ArrayList
<>(attributions
.getValues());
341 attrs
= attrs
.stream().sorted().distinct().collect(Collectors
.toList());
342 if(doJoinAttributions
){
343 attrs
.add(0, diversityInfo
+ "<br/>" + attrs
.get(0));
345 manifest
.addAttribution(attrs
.stream()
348 .collect(Collectors
.joining("; ")));
350 manifest
.addAttribution(diversityInfo
, attrs
.toArray(
351 new String
[attributions
.getValues().size()]
359 .forEachOrdered(l
-> manifest
.addLicense(l
));
363 private void orderMedatadaItems(Canvas canvas
) {
364 // TODO Auto-generated method stub
365 // order by label name, Title, description, author, license, attribution should come first.
368 private List
<MetadataEntry
> deduplicateMetadata(List
<MetadataEntry
> mediaMetadata
) {
369 Map
<String
, MetadataEntry
> dedupMap
= new HashMap
<>();
370 mediaMetadata
.stream().forEach(mde
-> {
371 String dedupKey
= mde
.getLabelString() + ":" + mde
.getValueString();
372 dedupMap
.put(dedupKey
, mde
);
375 return new ArrayList
<>(dedupMap
.values());
378 private void extractAndAddDesciptions(Resource resource
, List
<MetadataEntry
> mediaMetadata
) {
379 List
<MetadataEntry
> descriptions
= mediaMetadata
.stream()
380 .filter(mde
-> mde
.getLabelString().toLowerCase().matches(".*description.*|.*caption.*"))
381 .collect(Collectors
.toList());
382 mediaMetadata
.removeAll(descriptions
);
383 // FIXME deduplicate mde.getValueString()
384 // descriptions.sream ...
385 descriptions
.stream().forEach(mde
-> resource
.addDescription(mde
.getValueString()));
388 private <T
extends IdentifiableEntity
> List
<MetadataEntry
> entityMetadata(T entity
) {
390 List
<MetadataEntry
> metadata
= new ArrayList
<>();
391 if(entity
instanceof TaxonBase
){
392 List taggedTitle
= ((TaxonBase
)entity
).getTaggedTitle();
393 if(taggedTitle
!= null){
394 //FIXME taggedTitel to HTML!!!!
395 metadata
.add(new MetadataEntry(entity
.getClass().getSimpleName(), TaggedCacheHelper
.createString(taggedTitle
)));
398 String titleCache
= entity
.getTitleCache();
399 if(titleCache
!= null){
400 metadata
.add(new MetadataEntry(entity
.getClass().getSimpleName(), titleCache
));
408 * @deprecated unused as media metadata is now read via the mediaService,
409 * see {@link IMediaService#readResourceMetadataFiltered(MediaRepresentation)}
412 private List
<MetadataEntry
> mediaRepresentationMetaData(MediaRepresentation representation
) {
414 List
<MetadataEntry
> metadata
= new ArrayList
<>();
415 boolean needsPrefix
= representation
.getParts().size() > 1;
418 for (MediaRepresentationPart part
: representation
.getParts()) {
421 prefix
= "Part" + partIndex
+ " ";
423 if (part
.getUri() != null) {
425 CdmImageInfo cdmImageInfo
= mediaInfoFactory
.cdmImageInfo(part
.getUri(), true);
426 Map
<String
, String
> result
= cdmImageInfo
.getMetaData();
428 for (String key
: result
.keySet()) {
429 metadata
.add(new MetadataEntry(key
, result
.get(key
)));
432 } catch (IOException
| HttpException e
) {
433 logger
.error("Problem while loading image metadata", e
);
434 metadata
.add(new MetadataEntry(prefix
+ " Error:", "Problem while loading image metadata <br/><small>(" + e
.getLocalizedMessage() + ")</small>"));
442 private List
<MetadataEntry
> mediaMetaData(Media media
) {
443 List
<MetadataEntry
> metadata
= new ArrayList
<>();
444 List
<Language
> languages
= LocaleContext
.getLanguages();
446 if(media
.getTitle() != null){
447 // TODO get localized titleCache
448 metadata
.add(new MetadataEntry("Title", media
.getTitleCache()));
450 if(media
.getArtist() != null){
451 metadata
.add(new MetadataEntry("Artist", media
.getArtist().getTitleCache()));
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());
459 metadata
.add(new MetadataEntry(new PropertyValue("Description"), descriptionValues
));
461 if(media
.getMediaCreated() != null){
462 metadata
.add(new MetadataEntry("Created on", media
.getMediaCreated().toString())); // TODO is this correct to string conversion?
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());
472 metadata
.add(new MetadataEntry(new PropertyValue("Identifiers"), identifierValues
));
475 if(!media
.getSources().isEmpty()) {
476 PropertyValue descriptionValues
= new PropertyValue();
477 for(IdentifiableSource source
: media
.getSources()){
478 descriptionValues
.addValue(sourceAsHtml(source
));
480 metadata
.add(new MetadataEntry(new PropertyValue("Sources"), descriptionValues
));
485 private String
sourceAsHtml(IdentifiableSource source
) {
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) {
493 html
.append(" ").append(htmlLink(new URI("http://doi.org/" + citation
.getDoiString()), citation
.getDoiString()));
494 } catch (URISyntaxException e
) {
495 // IGNORE, should never happen
499 if(source
.getIdNamespace() != null && source
.getIdInSource() != null) {
500 html
.append(source
.getIdNamespace()).append("/").append(source
.getIdInSource()).append(" ");
503 String linkhtml
= null;
504 for(ExternalLink extLink
: source
.getLinks()) {
505 if(extLink
.getUri() != null) {
506 if(linkhtml
!= null) {
509 linkhtml
= htmlLink(extLink
.getUri().getJavaUri(), extLink
.getUri().toString());
510 html
.append(linkhtml
);
513 return html
.toString();
516 private <T
extends Resource
<T
>> T
addAttributionAndLicense(IdentifiableEntity
<?
> entity
, T resource
, List
<MetadataEntry
> metadata
) {
518 List
<Language
> languages
= LocaleContext
.getLanguages();
520 List
<String
> rightsTexts
= new ArrayList
<>();
521 List
<String
> creditTexts
= new ArrayList
<>();
522 List
<URI
> license
= new ArrayList
<>();
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();
535 if(right
.getAbbreviatedText() != null){
536 licenseAbbrev
= right
.getAbbreviatedText().trim();
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
);
544 licenseText
= htmlLink(right
.getUri().getJavaUri(), right
.getUri().toString());
546 license
.add(right
.getUri().getJavaUri());
548 rightText
= licenseAbbrev
+ (licenseText
.isEmpty() ?
"" : " ") + licenseText
;
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();
559 if(right
.getAgent() != null){
560 // may only apply to RightsType.accessRights
561 copyRightText
+= " " + right
.getAgent().getTitleCache();
563 if(!copyRightText
.isEmpty()){
564 copyRightText
= "© " + copyRightText
;
566 rightText
= copyRightText
;
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();
575 rightText
= accessRights
;
577 if(!rightText
.isEmpty()){
578 rightsTexts
.add(rightText
);
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();
588 if(creditText
.isEmpty() && credit
.getAbbreviatedText() != null){
589 creditText
+= credit
.getAbbreviatedText();
591 if(credit
.getAgent() != null){
592 // may only apply to RightsType.accessRights
593 creditText
+= " " + credit
.getAgent().getTitleCache();
595 creditTexts
.add(creditText
);
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
)));
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
)));
613 resource
.setLicenses(license
);
617 private String
htmlLink(URI uri
, String text
) {
618 return String
.format(" <a href=\"%s\">%s</a>", uri
, text
);
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());
631 if(image
.getHeight() != null && image
.getHeight() > 0){
632 ic
.setHeight(image
.getHeight());
634 if(representation
.getMimeType() != null){
635 ic
.setFormat(MimeType
.fromTypename(representation
.getMimeType()));
637 ic
.setFormat(MimeType
.MIME_IMAGE
);
640 imageContents
.add(ic
);
643 return imageContents
;
646 private String
iiifID(String onEntitiyType
, String onEntityUuid
, Class
<?
extends Resource
> iiifType
, Object index
) {
647 String indexPart
= "";
649 indexPart
= "/" + index
.toString();
651 return this.iiifIdPrefix
+ onEntitiyType
+ "/" + onEntityUuid
+ "/" + iiifType
.getSimpleName().toLowerCase() + indexPart
;