1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.opencastproject.metadata.dublincore;
23
24 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_ACCESS_RIGHTS;
25 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_AVAILABLE;
26 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_CONTRIBUTOR;
27 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_CREATED;
28 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_CREATOR;
29 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_DESCRIPTION;
30 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_EXTENT;
31 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_IDENTIFIER;
32 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_IS_PART_OF;
33 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_LANGUAGE;
34 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_LICENSE;
35 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_PUBLISHER;
36 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_REPLACES;
37 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_RIGHTS_HOLDER;
38 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_SPATIAL;
39 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_SUBJECT;
40 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_TEMPORAL;
41 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_TITLE;
42 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_TYPE;
43 import static org.opencastproject.util.data.Collections.head;
44
45 import org.opencastproject.mediapackage.Catalog;
46 import org.opencastproject.mediapackage.MediaPackage;
47 import org.opencastproject.mediapackage.MediaPackageElements;
48 import org.opencastproject.mediapackage.MediaPackageSerializer;
49 import org.opencastproject.metadata.api.MetadataValue;
50 import org.opencastproject.metadata.api.StaticMetadata;
51 import org.opencastproject.metadata.api.StaticMetadataService;
52 import org.opencastproject.metadata.api.util.Interval;
53 import org.opencastproject.util.data.NonEmptyList;
54 import org.opencastproject.workspace.api.Workspace;
55
56 import org.apache.commons.io.IOUtils;
57 import org.osgi.service.component.annotations.Activate;
58 import org.osgi.service.component.annotations.Component;
59 import org.osgi.service.component.annotations.Reference;
60 import org.osgi.service.component.annotations.ReferenceCardinality;
61 import org.osgi.service.component.annotations.ReferencePolicy;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64
65 import java.io.InputStream;
66 import java.net.URI;
67 import java.util.Date;
68 import java.util.List;
69 import java.util.Map;
70 import java.util.Optional;
71 import java.util.function.Function;
72 import java.util.stream.Collectors;
73
74
75
76
77
78 @Component(
79 immediate = true,
80 service = StaticMetadataService.class,
81 property = {
82 "service.description=Static Metadata Service, dublin core based",
83 "metadata.source=dublincore",
84 "priority=1"
85 }
86 )
87 public class StaticMetadataServiceDublinCoreImpl implements StaticMetadataService {
88
89 private static final Logger logger = LoggerFactory.getLogger(StaticMetadataServiceDublinCoreImpl.class);
90
91 protected int priority = 0;
92
93 protected Workspace workspace = null;
94
95 protected MediaPackageSerializer serializer = null;
96
97 @Reference
98 public void setWorkspace(Workspace workspace) {
99 this.workspace = workspace;
100 }
101
102 @Reference(
103 cardinality = ReferenceCardinality.OPTIONAL,
104 policy = ReferencePolicy.DYNAMIC,
105 target = "(service.pid=org.opencastproject.mediapackage.ChainingMediaPackageSerializer)",
106 unbind = "unsetMediaPackageSerializer"
107 )
108 public void setMediaPackageSerializer(MediaPackageSerializer serializer) {
109 this.serializer = serializer;
110 }
111
112 public void unsetMediaPackageSerializer(MediaPackageSerializer serializer) {
113 if (this.serializer == serializer) {
114 this.serializer = null;
115 }
116 }
117
118 @Activate
119 public void activate(@SuppressWarnings("rawtypes") Map properties) {
120 logger.debug("activate()");
121 if (properties != null) {
122 String priorityString = (String) properties.get(PRIORITY_KEY);
123 if (priorityString != null) {
124 try {
125 priority = Integer.parseInt(priorityString);
126 } catch (NumberFormatException e) {
127 logger.warn("Unable to set priority to {}", priorityString);
128 throw e;
129 }
130 }
131 }
132 }
133
134
135
136
137
138
139 @Override
140 public StaticMetadata getMetadata(final MediaPackage mp) {
141 Catalog[] catalogs = mp.getCatalogs(MediaPackageElements.EPISODE);
142 if (catalogs.length > 0) {
143 return newStaticMetadataFromEpisode(DublinCoreUtil.loadDublinCore(workspace, catalogs[0]));
144 }
145 return null;
146 }
147
148 private static StaticMetadata newStaticMetadataFromEpisode(DublinCoreCatalog episode) {
149
150 final Optional<String> id = Optional.ofNullable(episode.getFirst(PROPERTY_IDENTIFIER));
151 final Optional<Date> created = Optional.ofNullable(episode.getFirst(PROPERTY_CREATED))
152 .map(a -> {
153 Date date = EncodingSchemeUtils.decodeDate(a);
154 if (date == null) {
155 throw new RuntimeException(a + " does not conform to W3C-DTF encoding scheme.");
156 }
157 return date;
158 });
159 final Optional temporalOpt = Optional.ofNullable(episode.getFirstVal(PROPERTY_TEMPORAL))
160 .map(dc2temporalValueOption());
161 final Optional<Date> start;
162 if (episode.getFirst(PROPERTY_TEMPORAL) != null) {
163 DCMIPeriod period = EncodingSchemeUtils
164 .decodeMandatoryPeriod(episode.getFirst(PROPERTY_TEMPORAL));
165 start = Optional.ofNullable(period.getStart());
166 } else {
167 start = created;
168 }
169 final Optional<String> language = Optional.ofNullable(episode.getFirst(PROPERTY_LANGUAGE));
170 final Optional<Long> extent = head(episode.get(PROPERTY_EXTENT))
171 .map(a -> {
172 Long duration = EncodingSchemeUtils.decodeDuration(a);
173 if (duration == null) {
174 throw new RuntimeException(a + " does not conform to ISO8601 encoding scheme for durations.");
175 }
176 return duration;
177 });
178 final Optional<String> type = Optional.ofNullable(episode.getFirst(PROPERTY_TYPE));
179
180 final Optional<String> isPartOf = Optional.ofNullable(episode.getFirst(PROPERTY_IS_PART_OF));
181 final Optional<String> replaces = Optional.ofNullable(episode.getFirst(PROPERTY_REPLACES));
182 final Optional<Interval> available = head(episode.get(PROPERTY_AVAILABLE))
183 .flatMap(v -> {
184 DCMIPeriod p = EncodingSchemeUtils.decodePeriod(v);
185 if (p == null) {
186 throw new RuntimeException(v + " does not conform to W3C-DTF encoding scheme for periods");
187 }
188 return Optional.of(Interval.fromValues(p.getStart(), p.getEnd()));
189 });
190 final NonEmptyList<MetadataValue<String>> titles = new NonEmptyList<>(
191 episode.get(PROPERTY_TITLE).stream()
192 .map(dc2mvString(PROPERTY_TITLE.getLocalName()))
193 .collect(Collectors.toList())
194 );
195 final List<MetadataValue<String>> subjects = episode.get(PROPERTY_SUBJECT).stream()
196 .map(dc2mvString(PROPERTY_SUBJECT.getLocalName()))
197 .collect(Collectors.toList());
198 final List<MetadataValue<String>> creators = episode.get(PROPERTY_CREATOR).stream()
199 .map(dc2mvString(PROPERTY_CREATOR.getLocalName()))
200 .collect(Collectors.toList());
201 final List<MetadataValue<String>> publishers = episode.get(PROPERTY_PUBLISHER).stream()
202 .map(dc2mvString(PROPERTY_PUBLISHER.getLocalName()))
203 .collect(Collectors.toList());
204 final List<MetadataValue<String>> contributors = episode.get(PROPERTY_CONTRIBUTOR).stream()
205 .map(dc2mvString(PROPERTY_CONTRIBUTOR.getLocalName()))
206 .collect(Collectors.toList());
207 final List<MetadataValue<String>> description = episode.get(PROPERTY_DESCRIPTION).stream()
208 .map(dc2mvString(PROPERTY_DESCRIPTION.getLocalName()))
209 .collect(Collectors.toList());
210 final List<MetadataValue<String>> rightsHolders = episode.get(PROPERTY_RIGHTS_HOLDER).stream()
211 .map(dc2mvString(PROPERTY_RIGHTS_HOLDER.getLocalName()))
212 .collect(Collectors.toList());
213 final List<MetadataValue<String>> spatials = episode.get(PROPERTY_SPATIAL).stream()
214 .map(dc2mvString(PROPERTY_SPATIAL.getLocalName()))
215 .collect(Collectors.toList());
216 final List<MetadataValue<String>> accessRights = episode.get(PROPERTY_ACCESS_RIGHTS).stream()
217 .map(dc2mvString(PROPERTY_ACCESS_RIGHTS.getLocalName()))
218 .collect(Collectors.toList());
219 final List<MetadataValue<String>> licenses = episode.get(PROPERTY_LICENSE).stream()
220 .map(dc2mvString(PROPERTY_LICENSE.getLocalName()))
221 .collect(Collectors.toList());
222
223 return new StaticMetadata() {
224 @Override
225 public Optional<String> getId() {
226 return id;
227 }
228
229 @Override
230 public Optional<Date[]> getTemporalPeriod() {
231 if (temporalOpt.isPresent()) {
232 if (temporalOpt.get() instanceof DCMIPeriod) {
233 DCMIPeriod p = (DCMIPeriod) temporalOpt.get();
234 return Optional.ofNullable(new Date[] { p.getStart(), p.getEnd() });
235 }
236 }
237 return Optional.empty();
238 }
239
240 @Override
241 public Optional<Date> getTemporalInstant() {
242 if (temporalOpt.isPresent()) {
243 if (temporalOpt.get() instanceof Date) {
244 return temporalOpt;
245 }
246 }
247 return Optional.empty();
248 }
249
250 @Override
251 public Optional<Long> getTemporalDuration() {
252 if (temporalOpt.isPresent()) {
253 if (temporalOpt.get() instanceof Long) {
254 return temporalOpt;
255 }
256 }
257 return Optional.empty();
258 }
259
260 @Override
261 public Optional<Long> getExtent() {
262 return extent;
263 }
264
265 @Override
266 public Optional<String> getLanguage() {
267 return language;
268 }
269
270 @Override
271 public Optional<String> getIsPartOf() {
272 return isPartOf;
273 }
274
275 @Override
276 public Optional<String> getReplaces() {
277 return replaces;
278 }
279
280 @Override
281 public Optional<String> getType() {
282 return type;
283 }
284
285 @Override
286 public Optional<Interval> getAvailable() {
287 return available;
288 }
289
290 @Override
291 public NonEmptyList<MetadataValue<String>> getTitles() {
292 return titles;
293 }
294
295 @Override
296 public List<MetadataValue<String>> getSubjects() {
297 return subjects;
298 }
299
300 @Override
301 public List<MetadataValue<String>> getCreators() {
302 return creators;
303 }
304
305 @Override
306 public List<MetadataValue<String>> getPublishers() {
307 return publishers;
308 }
309
310 @Override
311 public List<MetadataValue<String>> getContributors() {
312 return contributors;
313 }
314
315 @Override
316 public List<MetadataValue<String>> getDescription() {
317 return description;
318 }
319
320 @Override
321 public List<MetadataValue<String>> getRightsHolders() {
322 return rightsHolders;
323 }
324
325 @Override
326 public List<MetadataValue<String>> getSpatials() {
327 return spatials;
328 }
329
330 @Override
331 public List<MetadataValue<String>> getAccessRights() {
332 return accessRights;
333 }
334
335 @Override
336 public List<MetadataValue<String>> getLicenses() {
337 return licenses;
338 }
339 };
340 }
341
342
343
344
345
346
347
348 @Override
349 public int getPriority() {
350 return priority;
351 }
352
353
354
355
356 private static java.util.function.Function<DublinCoreValue, Object> dc2temporalValueOption() {
357 return dcv -> {
358 Temporal temporal = EncodingSchemeUtils.decodeTemporal(dcv);
359 if (temporal == null) {
360 throw new RuntimeException(dcv
361 + " does not conform to ISO8601 encoding scheme for temporal.");
362 }
363 return temporal.fold(new Temporal.Match<Object>() {
364 @Override
365 public Object period(DCMIPeriod period) {
366 return period;
367 }
368
369 @Override
370 public Object instant(Date instant) {
371 return instant;
372 }
373
374 @Override
375 public Object duration(long duration) {
376 return duration;
377 }
378 });
379 };
380 }
381
382
383
384
385
386 private static Function<DublinCoreValue, MetadataValue<String>> dc2mvString(final String name) {
387 return dcv -> new MetadataValue<>(dcv.getValue(), name, dcv.getLanguage());
388 }
389
390 private Optional<DublinCoreCatalog> load(Catalog catalog) {
391 InputStream in = null;
392 try {
393 URI uri = catalog.getURI();
394 if (serializer != null) {
395 uri = serializer.decodeURI(uri);
396 }
397 in = workspace.read(uri);
398 return Optional.of((DublinCoreCatalog) DublinCores.read(in));
399 } catch (Exception e) {
400 logger.warn("Unable to load metadata from catalog '{}'", catalog);
401 return Optional.empty();
402 } finally {
403 IOUtils.closeQuietly(in);
404 }
405 }
406 }