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)).map(dc2temporalValueOption());
160 final Optional<Date> start;
161 if (episode.getFirst(PROPERTY_TEMPORAL) != null) {
162 DCMIPeriod period = EncodingSchemeUtils
163 .decodeMandatoryPeriod(episode.getFirst(PROPERTY_TEMPORAL));
164 start = Optional.ofNullable(period.getStart());
165 } else {
166 start = created;
167 }
168 final Optional<String> language = Optional.ofNullable(episode.getFirst(PROPERTY_LANGUAGE));
169 final Optional<Long> extent = head(episode.get(PROPERTY_EXTENT))
170 .map(a -> {
171 Long duration = EncodingSchemeUtils.decodeDuration(a);
172 if (duration == null) {
173 throw new RuntimeException(a + " does not conform to ISO8601 encoding scheme for durations.");
174 }
175 return duration;
176 });
177 final Optional<String> type = Optional.ofNullable(episode.getFirst(PROPERTY_TYPE));
178
179 final Optional<String> isPartOf = Optional.ofNullable(episode.getFirst(PROPERTY_IS_PART_OF));
180 final Optional<String> replaces = Optional.ofNullable(episode.getFirst(PROPERTY_REPLACES));
181 final Optional<Interval> available = head(episode.get(PROPERTY_AVAILABLE))
182 .flatMap(v -> {
183 DCMIPeriod p = EncodingSchemeUtils.decodePeriod(v);
184 if (p == null) {
185 throw new RuntimeException(v + " does not conform to W3C-DTF encoding scheme for periods");
186 }
187 return Optional.of(Interval.fromValues(p.getStart(), p.getEnd()));
188 });
189 final NonEmptyList<MetadataValue<String>> titles = new NonEmptyList<>(
190 episode.get(PROPERTY_TITLE).stream()
191 .map(dc2mvString(PROPERTY_TITLE.getLocalName()))
192 .collect(Collectors.toList())
193 );
194 final List<MetadataValue<String>> subjects = episode.get(PROPERTY_SUBJECT).stream()
195 .map(dc2mvString(PROPERTY_SUBJECT.getLocalName()))
196 .collect(Collectors.toList());
197 final List<MetadataValue<String>> creators = episode.get(PROPERTY_CREATOR).stream()
198 .map(dc2mvString(PROPERTY_CREATOR.getLocalName()))
199 .collect(Collectors.toList());
200 final List<MetadataValue<String>> publishers = episode.get(PROPERTY_PUBLISHER).stream()
201 .map(dc2mvString(PROPERTY_PUBLISHER.getLocalName()))
202 .collect(Collectors.toList());
203 final List<MetadataValue<String>> contributors = episode.get(PROPERTY_CONTRIBUTOR).stream()
204 .map(dc2mvString(PROPERTY_CONTRIBUTOR.getLocalName()))
205 .collect(Collectors.toList());
206 final List<MetadataValue<String>> description = episode.get(PROPERTY_DESCRIPTION).stream()
207 .map(dc2mvString(PROPERTY_DESCRIPTION.getLocalName()))
208 .collect(Collectors.toList());
209 final List<MetadataValue<String>> rightsHolders = episode.get(PROPERTY_RIGHTS_HOLDER).stream()
210 .map(dc2mvString(PROPERTY_RIGHTS_HOLDER.getLocalName()))
211 .collect(Collectors.toList());
212 final List<MetadataValue<String>> spatials = episode.get(PROPERTY_SPATIAL).stream()
213 .map(dc2mvString(PROPERTY_SPATIAL.getLocalName()))
214 .collect(Collectors.toList());
215 final List<MetadataValue<String>> accessRights = episode.get(PROPERTY_ACCESS_RIGHTS).stream()
216 .map(dc2mvString(PROPERTY_ACCESS_RIGHTS.getLocalName()))
217 .collect(Collectors.toList());
218 final List<MetadataValue<String>> licenses = episode.get(PROPERTY_LICENSE).stream()
219 .map(dc2mvString(PROPERTY_LICENSE.getLocalName()))
220 .collect(Collectors.toList());
221
222 return new StaticMetadata() {
223 @Override
224 public Optional<String> getId() {
225 return id;
226 }
227
228 @Override
229 public Optional<Date[]> getTemporalPeriod() {
230 if (temporalOpt.isPresent()) {
231 if (temporalOpt.get() instanceof DCMIPeriod) {
232 DCMIPeriod p = (DCMIPeriod) temporalOpt.get();
233 return Optional.ofNullable(new Date[] { p.getStart(), p.getEnd() });
234 }
235 }
236 return Optional.empty();
237 }
238
239 @Override
240 public Optional<Date> getTemporalInstant() {
241 if (temporalOpt.isPresent()) {
242 if (temporalOpt.get() instanceof Date) {
243 return temporalOpt;
244 }
245 }
246 return Optional.empty();
247 }
248
249 @Override
250 public Optional<Long> getTemporalDuration() {
251 if (temporalOpt.isPresent()) {
252 if (temporalOpt.get() instanceof Long) {
253 return temporalOpt;
254 }
255 }
256 return Optional.empty();
257 }
258
259 @Override
260 public Optional<Long> getExtent() {
261 return extent;
262 }
263
264 @Override
265 public Optional<String> getLanguage() {
266 return language;
267 }
268
269 @Override
270 public Optional<String> getIsPartOf() {
271 return isPartOf;
272 }
273
274 @Override
275 public Optional<String> getReplaces() {
276 return replaces;
277 }
278
279 @Override
280 public Optional<String> getType() {
281 return type;
282 }
283
284 @Override
285 public Optional<Interval> getAvailable() {
286 return available;
287 }
288
289 @Override
290 public NonEmptyList<MetadataValue<String>> getTitles() {
291 return titles;
292 }
293
294 @Override
295 public List<MetadataValue<String>> getSubjects() {
296 return subjects;
297 }
298
299 @Override
300 public List<MetadataValue<String>> getCreators() {
301 return creators;
302 }
303
304 @Override
305 public List<MetadataValue<String>> getPublishers() {
306 return publishers;
307 }
308
309 @Override
310 public List<MetadataValue<String>> getContributors() {
311 return contributors;
312 }
313
314 @Override
315 public List<MetadataValue<String>> getDescription() {
316 return description;
317 }
318
319 @Override
320 public List<MetadataValue<String>> getRightsHolders() {
321 return rightsHolders;
322 }
323
324 @Override
325 public List<MetadataValue<String>> getSpatials() {
326 return spatials;
327 }
328
329 @Override
330 public List<MetadataValue<String>> getAccessRights() {
331 return accessRights;
332 }
333
334 @Override
335 public List<MetadataValue<String>> getLicenses() {
336 return licenses;
337 }
338 };
339 }
340
341
342
343
344
345
346
347 @Override
348 public int getPriority() {
349 return priority;
350 }
351
352
353
354
355 private static java.util.function.Function<DublinCoreValue, Object> dc2temporalValueOption() {
356 return dcv -> {
357 Temporal temporal = EncodingSchemeUtils.decodeTemporal(dcv);
358 if (temporal == null) {
359 throw new RuntimeException(dcv
360 + " does not conform to ISO8601 encoding scheme for temporal.");
361 }
362 return temporal.fold(new Temporal.Match<Object>() {
363 @Override
364 public Object period(DCMIPeriod period) { return period; }
365
366 @Override
367 public Object instant(Date instant) { return instant; }
368
369 @Override
370 public Object duration(long duration) { return duration; }
371 });
372 };
373 }
374
375
376
377
378 private static Function<DublinCoreValue, MetadataValue<String>> dc2mvString(final String name) {
379 return dcv -> new MetadataValue<>(dcv.getValue(), name, dcv.getLanguage());
380 }
381
382 private Optional<DublinCoreCatalog> load(Catalog catalog) {
383 InputStream in = null;
384 try {
385 URI uri = catalog.getURI();
386 if (serializer != null) uri = serializer.decodeURI(uri);
387 in = workspace.read(uri);
388 return Optional.of((DublinCoreCatalog) DublinCores.read(in));
389 } catch (Exception e) {
390 logger.warn("Unable to load metadata from catalog '{}'", catalog);
391 return Optional.empty();
392 } finally {
393 IOUtils.closeQuietly(in);
394 }
395 }
396 }