48b5699a8d8798aaef11c6cc99515f3f407bef59
[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.Objects;
19 import java.util.Optional;
20 import java.util.stream.Collectors;
21
22 import org.apache.http.HttpException;
23 import org.apache.logging.log4j.LogManager;
24 import org.apache.logging.log4j.Logger;
25
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;
59
60 /**
61 * Factory class for creating iiif manifests.
62 * <p>
63 * This class is not state less therefore it is not a spring bean.
64 *
65 * @author a.kohlbecker
66 * @since Aug 18, 2020
67 */
68 public class ManifestComposer {
69
70 private static final Logger logger = LogManager.getLogger();
71
72 private IMediaToolbox mediaTools;
73
74 private IMediaService mediaService;
75
76 private MediaInfoFactory mediaInfoFactory; // FIXME define and use interface
77
78 private String iiifIdPrefix;
79
80 private String[] thumbnailMimetypes = new String[] {"image/.*", ".*"};
81
82
83 private boolean doJoinAttributions = false;
84
85 private boolean useThumbnailDimensionsForCanvas = false;
86
87
88 public String getIiifIdPrefix() {
89 return iiifIdPrefix;
90 }
91
92 public void setIiifIdPrefix(String iiifIdPrefix) {
93 this.iiifIdPrefix = iiifIdPrefix;
94 }
95
96
97 public String[] getThumbnailMimetypes() {
98 return thumbnailMimetypes;
99 }
100
101 public void setThumbnailMimetypes(String[] thumbnailMimetypes) {
102 this.thumbnailMimetypes = thumbnailMimetypes;
103 }
104
105 public boolean isDoJoinAttributions() {
106 return doJoinAttributions;
107 }
108
109 /**
110 * Universal viewer only shows one attribution value in the popup panel
111 * Therefore it makes sense to join all of them.
112 */
113 public void setDoJoinAttributions(boolean doJoinAttributions) {
114 this.doJoinAttributions = doJoinAttributions;
115 }
116
117 public boolean isUseThumbnailDimensionsForCanvas() {
118 return useThumbnailDimensionsForCanvas;
119 }
120
121 /**
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}
127 *
128 */
129 public void setUseThumbnailDimensionsForCanvas(boolean useThumbnailDimensionsForCanvas) {
130 this.useThumbnailDimensionsForCanvas = useThumbnailDimensionsForCanvas;
131 }
132
133
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;
139 }
140
141 <T extends IdentifiableEntity> Manifest manifestFor(EntityMediaContext<T> entityMediaContext, String onEntitiyType, String onEntityUuid) throws IOException {
142
143 // LogUtils.setLevel(MediaUtils.class, Level.DEBUG);
144 // LogUtils.setLevel(logger, Level.DEBUG);
145 List<Canvas> canvases = null;
146 try {
147 canvases = entityMediaContext.getMedia().parallelStream().map(m -> {
148 try {
149 return createCanvas(onEntitiyType, onEntityUuid, m);
150 } catch (IOException e) {
151 throw new RuntimeException(e);
152 }
153 })
154 .filter(Optional::isPresent)
155 .map(Optional::get)
156 .collect(Collectors.toList());
157 } catch(RuntimeException re) {
158 // re-throw IOException from lambda expression
159 throw new IOException(re.getCause());
160 }
161
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());
168 }
169
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);
174 } else {
175 manifest.setLabel(new PropertyValue("No media found for " + onEntitiyType + "[" + onEntityUuid + "]")); // TODO better label!!
176 }
177 List<MetadataEntry> entityMetadata = entityMetadata(entityMediaContext.getEntity());
178 manifest.addMetadata(entityMetadata.toArray(new MetadataEntry[entityMetadata.size()]));
179 copyAttributionAndLicenseToManifest(manifest);
180
181 return manifest;
182 }
183
184 public Optional<Canvas> createCanvas(String onEntitiyType, String onEntityUuid, Media media) throws IOException {
185 Canvas canvas;
186
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());
194 }
195
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);
201 } else {
202 fullSizeImageContents = new ArrayList<>(0);
203 }
204
205 if(Objects.equals(fullSizeRepresentation, thumbnailRepresentation)){
206 thumbnailImageContents = fullSizeImageContents;
207 } else {
208 thumbnailImageContents = representationPartsToImageContent(thumbnailRepresentation);
209 }
210
211 if(fullSizeRepresentation == null) {
212 return Optional.empty();
213 }
214
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());
219 }
220 canvas.setLabel(new PropertyValue(media.getTitleCache()));
221 canvas.setThumbnails(thumbnailImageContents);
222 for(ImageContent image : fullSizeImageContents){
223 canvas.addImage(image);
224 }
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:
227 //
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
233 // of the image.
234
235 // apply hack for accurate thumbnail container aspect ratios see setUseThumbnailDimensionsForCanvas() for an
236 // explanation
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());
241 }
242 }
243
244 List<MetadataEntry> mediaMetadata = mediaMetaData(media);
245 List<MetadataEntry> representationMetadata;
246 try {
247 representationMetadata = mediaService.readResourceMetadataFiltered(fullSizeRepresentation)
248 .entrySet()
249 .stream()
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);
256 }
257
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);
264 }
265
266 /**
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
270 *
271 * <b>NOTE:</b> This method expects that the canvas attributions and
272 * licenses are not localized!!!!
273 *
274 * @param canvas
275 */
276 private void copyAttributionAndLicenseToManifest(Manifest manifest) {
277
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;
286
287 if(manifest.getSequences() == null){
288 // nothing to do, skip!
289 return;
290 }
291
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()
297 .stream()
298 .sorted()
299 .collect(Collectors.joining());
300 if(firstAttributionString == null){
301 firstAttributionString = thisAttributionString;
302 hasAttributions = true;
303 } else {
304 hasDiversAttributions |= !firstAttributionString.equals(thisAttributionString);
305 }
306 }
307 if (canvas.getLicenses() != null && canvas.getLicenses().size() > 0) {
308 licenses.addAll(canvas.getLicenses());
309 String thisLicensesString = canvas.getLicenses()
310 .stream()
311 .map(URI::toString)
312 .sorted()
313 .collect(Collectors.joining());
314 if(firstLicensesString == null){
315 firstLicensesString = thisLicensesString;
316 hasLicenses = true;
317 } else {
318 hasDiversLicenses |= !firstLicensesString.equals(thisLicensesString);
319 }
320 }
321 }
322 }
323 String diversityInfo = "";
324
325 if(hasAttributions || hasLicenses){
326 String dataTypes ;
327 if(hasAttributions && hasLicenses) {
328 dataTypes = "attributions and licenses";
329 } else if(hasAttributions){
330 dataTypes = "attributions";
331 } else {
332 dataTypes = "licenses";
333 }
334 if(hasDiversAttributions || hasDiversLicenses){
335 diversityInfo = "Individual " + dataTypes + " per Item:";
336 } else {
337 diversityInfo = "Same " + dataTypes + " for any Item:";
338 }
339 if(hasAttributions){
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));
344 attrs.remove(1);
345 manifest.addAttribution(attrs.stream()
346 .sorted()
347 .distinct()
348 .collect(Collectors.joining("; ")));
349 } else {
350 manifest.addAttribution(diversityInfo, attrs.toArray(
351 new String[attributions.getValues().size()]
352 ));
353 }
354 }
355 licenses.stream()
356 .map(URI::toString)
357 .sorted()
358 .distinct()
359 .forEachOrdered(l -> manifest.addLicense(l));
360 }
361 }
362
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.
366 }
367
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);
373 }
374 );
375 return new ArrayList<>(dedupMap.values());
376 }
377
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()));
386 }
387
388 private <T extends IdentifiableEntity> List<MetadataEntry> entityMetadata(T entity) {
389
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)));
396 }
397 } else {
398 String titleCache = entity.getTitleCache();
399 if(titleCache != null){
400 metadata.add(new MetadataEntry(entity.getClass().getSimpleName(), titleCache));
401 }
402 }
403
404 return metadata;
405 }
406
407 /**
408 * @deprecated unused as media metadata is now read via the mediaService,
409 * see {@link IMediaService#readResourceMetadataFiltered(MediaRepresentation)}
410 */
411 @Deprecated
412 private List<MetadataEntry> mediaRepresentationMetaData(MediaRepresentation representation) {
413
414 List<MetadataEntry> metadata = new ArrayList<>();
415 boolean needsPrefix = representation.getParts().size() > 1;
416 int partIndex = 1;
417
418 for (MediaRepresentationPart part : representation.getParts()) {
419 String prefix = "";
420 if (needsPrefix) {
421 prefix = "Part" + partIndex + " ";
422 }
423 if (part.getUri() != null) {
424 try {
425 CdmImageInfo cdmImageInfo = mediaInfoFactory.cdmImageInfo(part.getUri(), true);
426 Map<String, String> result = cdmImageInfo.getMetaData();
427 if(result != null){
428 for (String key : result.keySet()) {
429 metadata.add(new MetadataEntry(key, result.get(key)));
430 }
431 }
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>"));
435 }
436 }
437 }
438
439 return metadata;
440 }
441
442 private List<MetadataEntry> mediaMetaData(Media media) {
443 List<MetadataEntry> metadata = new ArrayList<>();
444 List<Language> languages = LocaleContext.getLanguages();
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 }