rename DefaultReferenceCacheStrategy to ReferenceDefaultCacheStrategy
[cdmlib.git] / cdmlib-remote / src / main / java / eu / etaxonomy / cdm / remote / controller / iiif / ManifestComposer.java
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.stream.Collectors;
19
20 import org.apache.http.HttpException;
21 import org.apache.log4j.Logger;
22
23 import de.digitalcollections.iiif.model.ImageContent;
24 import de.digitalcollections.iiif.model.MetadataEntry;
25 import de.digitalcollections.iiif.model.MimeType;
26 import de.digitalcollections.iiif.model.PropertyValue;
27 import de.digitalcollections.iiif.model.enums.ViewingDirection;
28 import de.digitalcollections.iiif.model.sharedcanvas.Canvas;
29 import de.digitalcollections.iiif.model.sharedcanvas.Manifest;
30 import de.digitalcollections.iiif.model.sharedcanvas.Resource;
31 import de.digitalcollections.iiif.model.sharedcanvas.Sequence;
32 import eu.etaxonomy.cdm.api.service.IMediaService;
33 import eu.etaxonomy.cdm.api.service.MediaServiceImpl;
34 import eu.etaxonomy.cdm.api.service.l10n.LocaleContext;
35 import eu.etaxonomy.cdm.common.media.CdmImageInfo;
36 import eu.etaxonomy.cdm.model.common.Credit;
37 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
38 import eu.etaxonomy.cdm.model.common.IdentifiableSource;
39 import eu.etaxonomy.cdm.model.common.Identifier;
40 import eu.etaxonomy.cdm.model.common.Language;
41 import eu.etaxonomy.cdm.model.common.LanguageString;
42 import eu.etaxonomy.cdm.model.media.ExternalLink;
43 import eu.etaxonomy.cdm.model.media.ImageFile;
44 import eu.etaxonomy.cdm.model.media.Media;
45 import eu.etaxonomy.cdm.model.media.MediaRepresentation;
46 import eu.etaxonomy.cdm.model.media.MediaRepresentationPart;
47 import eu.etaxonomy.cdm.model.media.MediaUtils;
48 import eu.etaxonomy.cdm.model.media.Rights;
49 import eu.etaxonomy.cdm.model.media.RightsType;
50 import eu.etaxonomy.cdm.model.reference.Reference;
51 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
52 import eu.etaxonomy.cdm.remote.controller.TaxonPortalController.EntityMediaContext;
53 import eu.etaxonomy.cdm.remote.controller.util.IMediaToolbox;
54 import eu.etaxonomy.cdm.strategy.cache.TaggedCacheHelper;
55 import eu.etaxonomy.cdm.strategy.cache.reference.ReferenceDefaultCacheStrategy;
56
57 /**
58 * Factory class for creating iiif manifests.
59 * <p>
60 * This class is not state less therefore it is not a spring bean.
61 *
62 * @author a.kohlbecker
63 * @since Aug 18, 2020
64 */
65 public class ManifestComposer {
66
67 public static final Logger logger = Logger.getLogger(ManifestComposer.class);
68
69 private IMediaToolbox mediaTools;
70
71 private IMediaService mediaService;
72
73 private String iiifIdPrefix;
74
75 private String[] thumbnailMimetypes = new String[] {"image/.*", ".*"};
76
77
78 private boolean doJoinAttributions = false;
79
80 private boolean useThumbnailDimensionsForCanvas = false;
81
82
83 public String getIiifIdPrefix() {
84 return iiifIdPrefix;
85 }
86
87 public void setIiifIdPrefix(String iiifIdPrefix) {
88 this.iiifIdPrefix = iiifIdPrefix;
89 }
90
91
92 public String[] getThumbnailMimetypes() {
93 return thumbnailMimetypes;
94 }
95
96 public void setThumbnailMimetypes(String[] thumbnailMimetypes) {
97 this.thumbnailMimetypes = thumbnailMimetypes;
98 }
99
100 public boolean isDoJoinAttributions() {
101 return doJoinAttributions;
102 }
103
104 /**
105 * Universal viewer only shows one attribution value in the popup panel
106 * Therefore it makes sense to join all of them.
107 */
108 public void setDoJoinAttributions(boolean doJoinAttributions) {
109 this.doJoinAttributions = doJoinAttributions;
110 }
111
112 public boolean isUseThumbnailDimensionsForCanvas() {
113 return useThumbnailDimensionsForCanvas;
114 }
115
116 /**
117 * Width and height of the thumbnail image will be used for the canvas size when this is true.
118 * Normally the canvas dimensions conform to the image dimension.
119 * This trick is necessary to achieve a pleasant presentation of the thumbnails in universal viewer,
120 * see {@linkplain https://dev.e-taxonomy.eu/redmine/issues/9132#note-21} and
121 * {@linkplain https://github.com/UniversalViewer/universalviewer/issues/743}
122 *
123 */
124 public void setUseThumbnailDimensionsForCanvas(boolean useThumbnailDimensionsForCanvas) {
125 this.useThumbnailDimensionsForCanvas = useThumbnailDimensionsForCanvas;
126 }
127
128
129 public ManifestComposer(String iiifIdPrefix, IMediaToolbox mediaTools, IMediaService mediaService) {
130 this.mediaTools = mediaTools;
131 this.iiifIdPrefix = iiifIdPrefix;
132 this.mediaService = mediaService;
133 }
134
135 <T extends IdentifiableEntity> Manifest manifestFor(EntityMediaContext<T> entityMediaContext, String onEntitiyType, String onEntityUuid) throws IOException {
136
137 List<Canvas> canvases = new ArrayList<>(entityMediaContext.getMedia().size());
138
139 // Logger.getLogger(MediaUtils.class).setLevel(Level.DEBUG);
140 // logger.setLevel(Level.DEBUG);
141
142 int mediaID = 0;
143 for(Media media : entityMediaContext.getMedia()){
144
145 MediaRepresentation thumbnailRepresentation = mediaTools.processAndFindBestMatchingRepresentation(media, null, null, 100, 100, thumbnailMimetypes, MediaUtils.MissingValueStrategy.MAX);
146 MediaRepresentation fullSizeRepresentation = mediaTools.processAndFindBestMatchingRepresentation(media, null, null, Integer.MAX_VALUE, Integer.MAX_VALUE, null, MediaUtils.MissingValueStrategy.MAX);
147 // MediaRepresentation fullSizeRepresentation = MediaUtils.findBestMatchingRepresentation(media, null, null, Integer.MAX_VALUE, Integer.MAX_VALUE, null, MediaUtils.MissingValueStrategy.MAX);
148 // MediaRepresentation thumbnailRepresentation = MediaUtils.findBestMatchingRepresentation(media, null, null, 100, 100, tumbnailMimetypes, MediaUtils.MissingValueStrategy.MAX);
149 if(logger.isDebugEnabled()){
150 logger.debug("fullSizeRepresentation: " + fullSizeRepresentation.getParts().get(0).getUri());
151 logger.debug("thumbnailRepresentation: " + thumbnailRepresentation.getParts().get(0).getUri());
152 }
153
154 // FIXME the below only makes sense if the media is an Image!!!!!
155 List<ImageContent> fullSizeImageContents = representationPartsToImageContent(fullSizeRepresentation);
156
157 List<ImageContent> thumbnailImageContents;
158 if(fullSizeRepresentation.equals(thumbnailRepresentation)){
159 thumbnailImageContents = fullSizeImageContents;
160 } else {
161 thumbnailImageContents = representationPartsToImageContent(thumbnailRepresentation);
162 }
163
164 Canvas canvas = new Canvas(iiifID(onEntitiyType, onEntityUuid, Canvas.class, mediaID++));
165 for(Language lang : media.getAllTitles().keySet()){
166 LanguageString titleLocalized = media.getAllTitles().get(lang);
167 canvas.addLabel(titleLocalized.getText());
168 }
169 canvas.setLabel(new PropertyValue(media.getTitleCache()));
170 canvas.setThumbnails(thumbnailImageContents);
171 for(ImageContent image : fullSizeImageContents){
172 canvas.addImage(image);
173 }
174 // TODO if there is only one image canvas.addImage() internally sets the canvas width and height
175 // to the height of the image, for multiple images it is required to follow the specification:
176 //
177 // IIIF Presentation API 2.1.1:
178 // It is recommended that if there is (at the time of implementation) a single image that depicts the page,
179 // then the dimensions of the image are used as the dimensions of the canvas for simplicity. If there are
180 // multiple full images, then the dimensions of the largest image should be used. If the largest image’s
181 // dimensions are less than 1200 pixels on either edge, then the canvas’s dimensions should be double those
182 // of the image.
183
184 // apply hack for accurate thumbnail container aspect ratios see setUseThumbnailDimensionsForCanvas() for an
185 // explanation
186 if(useThumbnailDimensionsForCanvas && !thumbnailImageContents.isEmpty()) {
187 if(thumbnailImageContents.get(0).getHeight() != null && thumbnailImageContents.get(0).getHeight() > 0 && thumbnailImageContents.get(0).getWidth() != null && thumbnailImageContents.get(0).getWidth() > 0) {
188 canvas.setHeight(thumbnailImageContents.get(0).getHeight());
189 canvas.setWidth(thumbnailImageContents.get(0).getWidth());
190 }
191 }
192
193 List<MetadataEntry> mediaMetadata = mediaMetaData(media);
194 List<MetadataEntry> representationMetadata;
195 try {
196 representationMetadata = mediaService.readResourceMetadataFiltered(fullSizeRepresentation)
197 .entrySet()
198 .stream()
199 .map(e -> new MetadataEntry(e.getKey(), e.getValue())).collect(Collectors.toList());
200 mediaMetadata.addAll(representationMetadata);
201 } catch (IOException e) {
202 logger.error("Error reading media metadata", e);
203 } catch (HttpException e) {
204 logger.error("Error accessing remote media resource", e);
205 }
206
207 // extractAndAddDesciptions(canvas, mediaMetadata);
208 mediaMetadata = deduplicateMetadata(mediaMetadata);
209 canvas = addAttributionAndLicense(media, canvas, mediaMetadata);
210 orderMedatadaItems(canvas);
211 canvas.addMetadata(mediaMetadata.toArray(new MetadataEntry[mediaMetadata.size()]));
212 canvases.add(canvas);
213 }
214
215 Sequence sequence = null;
216 if(canvases.size() > 0) {
217 sequence = new Sequence(iiifID(onEntitiyType, onEntityUuid, Sequence.class, "default"));
218 sequence.setViewingDirection(ViewingDirection.LEFT_TO_RIGHT);
219 sequence.setCanvases(canvases);
220 sequence.setStartCanvas(canvases.get(0).getIdentifier());
221 }
222
223 Manifest manifest = new Manifest(iiifID(onEntitiyType, onEntityUuid, Manifest.class, null));
224 if(sequence != null){
225 // manifest.setLabel(new PropertyValue("Media for " + onEntitiyType + "[" + onEntityUuid + "]")); // TODO better label!!
226 manifest.addSequence(sequence);
227 } else {
228 manifest.setLabel(new PropertyValue("No media found for " + onEntitiyType + "[" + onEntityUuid + "]")); // TODO better label!!
229 }
230 List<MetadataEntry> entityMetadata = entityMetadata(entityMediaContext.getEntity());
231 manifest.addMetadata(entityMetadata.toArray(new MetadataEntry[entityMetadata.size()]));
232 copyAttributionAndLicenseToManifest(manifest);
233
234 return manifest;
235 }
236
237 /**
238 * Due to limitations in universal viewer it seems not to be able
239 * to show attribution and licenses, therefore we copy this data to
240 * also to the metadata
241 *
242 * <b>NOTE:</b> This method expects that the canvas attributions and
243 * licenses are not localized!!!!
244 *
245 * @param canvas
246 */
247 private void copyAttributionAndLicenseToManifest(Manifest manifest) {
248
249 PropertyValue attributions = new PropertyValue();
250 List<URI> licenses = new ArrayList<>();
251 String firstAttributionString = null;
252 boolean hasAttributions = false;
253 boolean hasLicenses = false;
254 boolean hasDiversAttributions = false;
255 boolean hasDiversLicenses = false;
256 String firstLicensesString = null;
257
258 if(manifest.getSequences() == null){
259 // nothing to do, skip!
260 return;
261 }
262
263 for (Sequence sequence : manifest.getSequences()) {
264 for (Canvas canvas : sequence.getCanvases()) {
265 if (canvas.getAttribution() != null) {
266 canvas.getAttribution().getValues().stream().forEachOrdered(val -> attributions.addValue(val));
267 String thisAttributionString = canvas.getAttribution().getValues()
268 .stream()
269 .sorted()
270 .collect(Collectors.joining());
271 if(firstAttributionString == null){
272 firstAttributionString = thisAttributionString;
273 hasAttributions = true;
274 } else {
275 hasDiversAttributions |= !firstAttributionString.equals(thisAttributionString);
276 }
277 }
278 if (canvas.getLicenses() != null && canvas.getLicenses().size() > 0) {
279 licenses.addAll(canvas.getLicenses());
280 String thisLicensesString = canvas.getLicenses()
281 .stream()
282 .map(URI::toString)
283 .sorted()
284 .collect(Collectors.joining());
285 if(firstLicensesString == null){
286 firstLicensesString = thisLicensesString;
287 hasLicenses = true;
288 } else {
289 hasDiversLicenses |= !firstLicensesString.equals(thisLicensesString);
290 }
291 }
292 }
293 }
294 String diversityInfo = "";
295
296 if(hasAttributions || hasLicenses){
297 String dataTypes ;
298 if(hasAttributions && hasLicenses) {
299 dataTypes = "attributions and licenses";
300 } else if(hasAttributions){
301 dataTypes = "attributions";
302 } else {
303 dataTypes = "licenses";
304 }
305 if(hasDiversAttributions || hasDiversLicenses){
306 diversityInfo = "Individual " + dataTypes + " per Item:";
307 } else {
308 diversityInfo = "Same " + dataTypes + " for any Item:";
309 }
310 if(hasAttributions){
311 List<String> attrs = new ArrayList<>(attributions.getValues());
312 attrs = attrs.stream().sorted().distinct().collect(Collectors.toList());
313 if(doJoinAttributions){
314 attrs.add(0, diversityInfo + "<br/>" + attrs.get(0));
315 attrs.remove(1);
316 manifest.addAttribution(attrs.stream()
317 .sorted()
318 .distinct()
319 .collect(Collectors.joining("; ")));
320 } else {
321 manifest.addAttribution(diversityInfo, attrs.toArray(
322 new String[attributions.getValues().size()]
323 ));
324 }
325 }
326 licenses.stream()
327 .map(URI::toString)
328 .sorted()
329 .distinct()
330 .forEachOrdered(l -> manifest.addLicense(l));
331 }
332 }
333
334 private void orderMedatadaItems(Canvas canvas) {
335 // TODO Auto-generated method stub
336 // order by label name, Title, description, author, license, attribution should come first.
337 }
338
339 private List<MetadataEntry> deduplicateMetadata(List<MetadataEntry> mediaMetadata) {
340 Map<String, MetadataEntry> dedupMap = new HashMap<>();
341 mediaMetadata.stream().forEach(mde -> {
342 String dedupKey = mde.getLabelString() + ":" + mde.getValueString();
343 dedupMap.put(dedupKey, mde);
344 }
345 );
346 return new ArrayList<>(dedupMap.values());
347 }
348
349 private void extractAndAddDesciptions(Resource resource, List<MetadataEntry> mediaMetadata) {
350 List<MetadataEntry> descriptions = mediaMetadata.stream()
351 .filter(mde -> mde.getLabelString().toLowerCase().matches(".*description.*|.*caption.*"))
352 .collect(Collectors.toList());
353 mediaMetadata.removeAll(descriptions);
354 // FIXME deduplicate mde.getValueString()
355 // descriptions.sream ...
356 descriptions.stream().forEach(mde -> resource.addDescription(mde.getValueString()));
357 }
358
359 private <T extends IdentifiableEntity> List<MetadataEntry> entityMetadata(T entity) {
360
361 List<MetadataEntry> metadata = new ArrayList<>();
362 if(entity instanceof TaxonBase){
363 List taggedTitle = ((TaxonBase)entity).getTaggedTitle();
364 if(taggedTitle != null){
365 //FIXME taggedTitel to HTML!!!!
366 metadata.add(new MetadataEntry(entity.getClass().getSimpleName(), TaggedCacheHelper.createString(taggedTitle)));
367 }
368 } else {
369 String titleCache = entity.getTitleCache();
370 if(titleCache != null){
371 metadata.add(new MetadataEntry(entity.getClass().getSimpleName(), titleCache));
372 }
373 }
374
375 return metadata;
376 }
377
378 /**
379 * @deprecated unused as media metadata is now read via the mediaService, see
380 */
381 @Deprecated
382 private List<MetadataEntry> mediaRepresentationMetaData(MediaRepresentation representation) {
383
384 List<MetadataEntry> metadata = new ArrayList<>();
385 boolean needsPrefix = representation.getParts().size() > 1;
386 int partIndex = 1;
387
388 for (MediaRepresentationPart part : representation.getParts()) {
389 String prefix = "";
390 if (needsPrefix) {
391 prefix = "Part" + partIndex + " ";
392 }
393 if (part.getUri() != null) {
394 try {
395 CdmImageInfo cdmImageInfo = CdmImageInfo.NewInstanceWithMetaData(part.getUri(), MediaServiceImpl.IMAGE_READ_TIMEOUT);
396 Map<String, String> result = cdmImageInfo.getMetaData();
397 if(result != null){
398 for (String key : result.keySet()) {
399 metadata.add(new MetadataEntry(key, result.get(key)));
400 }
401 }
402 } catch (IOException | HttpException e) {
403 logger.error("Problem while loading image metadata", e);
404 metadata.add(new MetadataEntry(prefix + " Error:", "Problem while loading image metadata <br/><small>(" + e.getLocalizedMessage() + ")</small>"));
405 }
406 }
407 }
408
409 return metadata;
410 }
411
412 private List<MetadataEntry> mediaMetaData(Media media) {
413 List<MetadataEntry> metadata = new ArrayList<>();
414 List<Language> languages = LocaleContext.getLanguages();
415
416
417 if(media.getTitle() != null){
418 // TODO get localized titleCache
419 metadata.add(new MetadataEntry("Title", media.getTitleCache()));
420 }
421 if(media.getArtist() != null){
422 metadata.add(new MetadataEntry("Artist", media.getArtist().getTitleCache()));
423 }
424 if(media.getAllDescriptions().size() > 0){
425 // TODO get localized description
426 PropertyValue descriptionValues = new PropertyValue();
427 for(LanguageString description : media.getAllDescriptions().values()){
428 descriptionValues.addValue(description.getText());
429 }
430 metadata.add(new MetadataEntry(new PropertyValue("Description"), descriptionValues));
431 }
432 if(media.getMediaCreated() != null){
433 metadata.add(new MetadataEntry("Created on", media.getMediaCreated().toString())); // TODO is this correct to string conversion?
434 }
435
436 if(!media.getIdentifiers().isEmpty()) {
437 PropertyValue identifierValues = new PropertyValue();
438 for(Identifier identifier : media.getIdentifiers()){
439 if(identifier.getIdentifier() != null) {
440 identifierValues.addValue(identifier.getIdentifier());
441 }
442 }
443 metadata.add(new MetadataEntry(new PropertyValue("Identifiers"), identifierValues));
444 }
445
446 if(!media.getSources().isEmpty()) {
447 PropertyValue descriptionValues = new PropertyValue();
448 for(IdentifiableSource source : media.getSources()){
449 descriptionValues.addValue(sourceAsHtml(source));
450 }
451 metadata.add(new MetadataEntry(new PropertyValue("Sources"), descriptionValues));
452 }
453 return metadata;
454 }
455
456 private String sourceAsHtml(IdentifiableSource source) {
457
458 StringBuilder html = new StringBuilder();
459 Reference citation = source.getCitation();
460 if(citation != null) {
461 ReferenceDefaultCacheStrategy strategy = ((ReferenceDefaultCacheStrategy)citation.getCacheStrategy());
462 html.append(strategy.createShortCitation(citation, source.getCitationMicroReference(), false)).append(" ");
463 if(citation.getDoi() != null) {
464 try {
465 html.append(" ").append(htmlLink(new URI("http://doi.org/" + citation.getDoiString()), citation.getDoiString()));
466 } catch (URISyntaxException e) {
467 // IGNORE, should never happen
468 }
469 }
470 }
471 if(source.getIdNamespace() != null && source.getIdInSource() != null) {
472 html.append(source.getIdNamespace()).append("/").append(source.getIdInSource()).append(" ");
473 }
474
475 String linkhtml = null;
476 for(ExternalLink extLink : source.getLinks()) {
477 if(extLink.getUri() != null) {
478 if(linkhtml != null) {
479 html.append(", ");
480 }
481 linkhtml = htmlLink(extLink.getUri().getJavaUri(), extLink.getUri().toString());
482 html.append(linkhtml);
483 }
484 }
485 return html.toString();
486 }
487
488 private <T extends Resource<T>> T addAttributionAndLicense(IdentifiableEntity<?> entity, T resource, List<MetadataEntry> metadata) {
489
490 List<Language> languages = LocaleContext.getLanguages();
491
492 List<String> rightsTexts = new ArrayList<>();
493 List<String> creditTexts = new ArrayList<>();
494 List<URI> license = new ArrayList<>();
495
496 if(entity.getRights() != null && entity.getRights().size() > 0){
497 for(Rights right : entity.getRights()){
498 String rightText = "";
499 // TODO get localized texts below
500 // --- LICENSE or NULL
501 if(right.getType() == null || right.getType().equals(RightsType.LICENSE())){
502 String licenseText = "";
503 String licenseAbbrev = "";
504 if(right.getText() != null){
505 licenseText = right.getText();
506 }
507 if(right.getAbbreviatedText() != null){
508 licenseAbbrev = right.getAbbreviatedText().trim();
509 }
510 if(right.getUri() != null){
511 if(!licenseAbbrev.isEmpty()) {
512 licenseAbbrev = htmlLink(right.getUri().getJavaUri(), licenseAbbrev);
513 } else if(!licenseText.isEmpty()) {
514 licenseText = htmlLink(right.getUri().getJavaUri(), licenseText);
515 } else {
516 licenseText = htmlLink(right.getUri().getJavaUri(), right.getUri().toString());
517 }
518 license.add(right.getUri().getJavaUri());
519 }
520 rightText = licenseAbbrev + (licenseText.isEmpty() ? "" : " ") + licenseText;
521 }
522 // --- COPYRIGHT
523 else if(right.getType().equals(RightsType.COPYRIGHT())){
524 // titleCache + agent
525 String copyRightText = "";
526 if(right.getText() != null){
527 copyRightText = right.getText();
528 // sanitize potential '(c)' away
529 copyRightText = copyRightText.replace("(c)", "").trim();
530 }
531 if(right.getAgent() != null){
532 // may only apply to RightsType.accessRights
533 copyRightText += " " + right.getAgent().getTitleCache();
534 }
535 if(!copyRightText.isEmpty()){
536 copyRightText = "© " + copyRightText;
537 }
538 rightText = copyRightText;
539 } else
540 if(right.getType().equals(RightsType.ACCESS_RIGHTS())){
541 // titleCache + agent
542 String accessRights = right.getText();
543 if(right.getAgent() != null){
544 // may only apply to RightsType.accessRights
545 accessRights = " " + right.getAgent().getTitleCache();
546 }
547 rightText = accessRights;
548 }
549 if(!rightText.isEmpty()){
550 rightsTexts.add(rightText);
551 }
552 }
553 }
554 if(entity.getCredits() != null && entity.getCredits().size() > 0){
555 for(Credit credit : entity.getCredits()){
556 String creditText = "";
557 if(credit.getText() != null){
558 creditText += credit.getText();
559 }
560 if(creditText.isEmpty() && credit.getAbbreviatedText() != null){
561 creditText += credit.getAbbreviatedText();
562 }
563 if(credit.getAgent() != null){
564 // may only apply to RightsType.accessRights
565 creditText += " " + credit.getAgent().getTitleCache();
566 }
567 creditTexts.add(creditText);
568 }
569 }
570
571 if(rightsTexts.size() > 0){
572 String joinedRights = rightsTexts.stream().collect(Collectors.joining(", "));
573 resource.addAttribution(joinedRights);
574 if(metadata != null){
575 metadata.add(new MetadataEntry(new PropertyValue("Copyright"), new PropertyValue(joinedRights)));
576 }
577 }
578 if(creditTexts.size() > 0){
579 String joinedCredits = creditTexts.stream().collect(Collectors.joining(", "));
580 resource.addAttribution(joinedCredits);
581 if(metadata != null){
582 metadata.add(new MetadataEntry(new PropertyValue("Credit"), new PropertyValue(joinedCredits)));
583 }
584 }
585 resource.setLicenses(license);
586 return resource;
587 }
588
589 private String htmlLink(URI uri, String text) {
590 return String.format(" <a href=\"%s\">%s</a>", uri, text);
591 }
592
593 private List<ImageContent> representationPartsToImageContent(MediaRepresentation representation) {
594 List<ImageContent> imageContents = new ArrayList<>();
595 for(MediaRepresentationPart part : representation.getParts()){
596 if(part.getUri() != null){
597 ImageContent ic = new ImageContent(part.getUri().toString());
598 if(part instanceof ImageFile){
599 ImageFile image = (ImageFile)part;
600 if(image.getWidth() != null && image.getWidth() > 0){
601 ic.setWidth(image.getWidth());
602 }
603 if(image.getHeight() != null && image.getHeight() > 0){
604 ic.setHeight(image.getHeight());
605 }
606 if(representation.getMimeType() != null){
607 ic.setFormat(MimeType.fromTypename(representation.getMimeType()));
608 } else {
609 ic.setFormat(MimeType.MIME_IMAGE);
610 }
611 }
612 imageContents.add(ic);
613 }
614 }
615 return imageContents;
616 }
617
618 private String iiifID(String onEntitiyType, String onEntityUuid, Class<? extends Resource> iiifType, Object index) {
619 String indexPart = "";
620 if(index != null){
621 indexPart = "/" + index.toString();
622 }
623 return this.iiifIdPrefix + onEntitiyType + "/" + onEntityUuid + "/" + iiifType.getSimpleName().toLowerCase() + indexPart;
624 }
625
626 }