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 import static org.opencastproject.util.data.Monadics.mlist;
45 import static org.opencastproject.util.data.Option.option;
46 import static org.opencastproject.util.data.Option.some;
47
48 import org.opencastproject.mediapackage.Catalog;
49 import org.opencastproject.mediapackage.MediaPackage;
50 import org.opencastproject.mediapackage.MediaPackageElementFlavor;
51 import org.opencastproject.mediapackage.MediaPackageElements;
52 import org.opencastproject.mediapackage.MediaPackageSerializer;
53 import org.opencastproject.metadata.api.MetadataValue;
54 import org.opencastproject.metadata.api.StaticMetadata;
55 import org.opencastproject.metadata.api.StaticMetadataService;
56 import org.opencastproject.metadata.api.util.Interval;
57 import org.opencastproject.util.data.Function;
58 import org.opencastproject.util.data.NonEmptyList;
59 import org.opencastproject.util.data.Option;
60 import org.opencastproject.util.data.Predicate;
61 import org.opencastproject.util.data.functions.Misc;
62 import org.opencastproject.workspace.api.Workspace;
63
64 import org.apache.commons.io.IOUtils;
65 import org.osgi.service.component.annotations.Activate;
66 import org.osgi.service.component.annotations.Component;
67 import org.osgi.service.component.annotations.Reference;
68 import org.osgi.service.component.annotations.ReferenceCardinality;
69 import org.osgi.service.component.annotations.ReferencePolicy;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
72
73 import java.io.InputStream;
74 import java.net.URI;
75 import java.util.Date;
76 import java.util.List;
77 import java.util.Map;
78
79
80
81
82
83 @Component(
84 immediate = true,
85 service = StaticMetadataService.class,
86 property = {
87 "service.description=Static Metadata Service, dublin core based",
88 "metadata.source=dublincore",
89 "priority=1"
90 }
91 )
92 public class StaticMetadataServiceDublinCoreImpl implements StaticMetadataService {
93
94 private static final Logger logger = LoggerFactory.getLogger(StaticMetadataServiceDublinCoreImpl.class);
95
96
97 private Function<Catalog, Option<DublinCoreCatalog>> loader = new Function<Catalog, Option<DublinCoreCatalog>>() {
98 @Override
99 public Option<DublinCoreCatalog> apply(Catalog catalog) {
100 return load(catalog);
101 }
102 };
103
104 protected int priority = 0;
105
106 protected Workspace workspace = null;
107
108 protected MediaPackageSerializer serializer = null;
109
110 @Reference
111 public void setWorkspace(Workspace workspace) {
112 this.workspace = workspace;
113 }
114
115 @Reference(
116 cardinality = ReferenceCardinality.OPTIONAL,
117 policy = ReferencePolicy.DYNAMIC,
118 target = "(service.pid=org.opencastproject.mediapackage.ChainingMediaPackageSerializer)",
119 unbind = "unsetMediaPackageSerializer"
120 )
121 public void setMediaPackageSerializer(MediaPackageSerializer serializer) {
122 this.serializer = serializer;
123 }
124
125 public void unsetMediaPackageSerializer(MediaPackageSerializer serializer) {
126 if (this.serializer == serializer) {
127 this.serializer = null;
128 }
129 }
130
131 @Activate
132 public void activate(@SuppressWarnings("rawtypes") Map properties) {
133 logger.debug("activate()");
134 if (properties != null) {
135 String priorityString = (String) properties.get(PRIORITY_KEY);
136 if (priorityString != null) {
137 try {
138 priority = Integer.parseInt(priorityString);
139 } catch (NumberFormatException e) {
140 logger.warn("Unable to set priority to {}", priorityString);
141 throw e;
142 }
143 }
144 }
145 }
146
147
148
149
150
151
152 @Override
153 public StaticMetadata getMetadata(final MediaPackage mp) {
154 Catalog[] catalogs = mp.getCatalogs(MediaPackageElements.EPISODE);
155 if (catalogs.length > 0) {
156 return newStaticMetadataFromEpisode(DublinCoreUtil.loadDublinCore(workspace, catalogs[0]));
157 }
158 return null;
159 }
160
161 private static StaticMetadata newStaticMetadataFromEpisode(DublinCoreCatalog episode) {
162
163 final Option<String> id = option(episode.getFirst(PROPERTY_IDENTIFIER));
164 final Option<Date> created = option(episode.getFirst(PROPERTY_CREATED)).map(new Function<String, Date>() {
165 @Override
166 public Date apply(String a) {
167 final Date date = EncodingSchemeUtils.decodeDate(a);
168 return date != null ? date : Misc.<Date>chuck(new RuntimeException(a + " does not conform to W3C-DTF encoding scheme."));
169 }
170 });
171 final Option temporalOpt = option(episode.getFirstVal(PROPERTY_TEMPORAL)).map(dc2temporalValueOption());
172 final Option<Date> start;
173 if (episode.getFirst(PROPERTY_TEMPORAL) != null) {
174 DCMIPeriod period = EncodingSchemeUtils
175 .decodeMandatoryPeriod(episode.getFirst(PROPERTY_TEMPORAL));
176 start = option(period.getStart());
177 } else {
178 start = created;
179 }
180 final Option<String> language = option(episode.getFirst(PROPERTY_LANGUAGE));
181 final Option<Long> extent = head(episode.get(PROPERTY_EXTENT)).map(new Function<DublinCoreValue, Long>() {
182 @Override
183 public Long apply(DublinCoreValue a) {
184 final Long extent = EncodingSchemeUtils.decodeDuration(a);
185 return extent != null ? extent : Misc.<Long>chuck(new RuntimeException(a + " does not conform to ISO8601 encoding scheme for durations."));
186 }
187 });
188 final Option<String> type = option(episode.getFirst(PROPERTY_TYPE));
189
190 final Option<String> isPartOf = option(episode.getFirst(PROPERTY_IS_PART_OF));
191 final Option<String> replaces = option(episode.getFirst(PROPERTY_REPLACES));
192 final Option<Interval> available = head(episode.get(PROPERTY_AVAILABLE)).flatMap(
193 new Function<DublinCoreValue, Option<Interval>>() {
194 @Override
195 public Option<Interval> apply(DublinCoreValue v) {
196 final DCMIPeriod p = EncodingSchemeUtils.decodePeriod(v);
197 return p != null
198 ? some(Interval.fromValues(p.getStart(), p.getEnd()))
199 : Misc.<Option<Interval>>chuck(new RuntimeException(v + " does not conform to W3C-DTF encoding scheme for periods"));
200 }
201 });
202 final NonEmptyList<MetadataValue<String>> titles = new NonEmptyList<MetadataValue<String>>(
203 mlist(episode.get(PROPERTY_TITLE)).map(dc2mvString(PROPERTY_TITLE.getLocalName())).value());
204 final List<MetadataValue<String>> subjects =
205 mlist(episode.get(PROPERTY_SUBJECT)).map(dc2mvString(PROPERTY_SUBJECT.getLocalName())).value();
206 final List<MetadataValue<String>> creators =
207 mlist(episode.get(PROPERTY_CREATOR)).map(dc2mvString(PROPERTY_CREATOR.getLocalName())).value();
208 final List<MetadataValue<String>> publishers =
209 mlist(episode.get(PROPERTY_PUBLISHER)).map(dc2mvString(PROPERTY_PUBLISHER.getLocalName())).value();
210 final List<MetadataValue<String>> contributors =
211 mlist(episode.get(PROPERTY_CONTRIBUTOR)).map(dc2mvString(PROPERTY_CONTRIBUTOR.getLocalName())).value();
212 final List<MetadataValue<String>> description =
213 mlist(episode.get(PROPERTY_DESCRIPTION)).map(dc2mvString(PROPERTY_DESCRIPTION.getLocalName())).value();
214 final List<MetadataValue<String>> rightsHolders =
215 mlist(episode.get(PROPERTY_RIGHTS_HOLDER)).map(dc2mvString(PROPERTY_RIGHTS_HOLDER.getLocalName())).value();
216 final List<MetadataValue<String>> spatials =
217 mlist(episode.get(PROPERTY_SPATIAL)).map(dc2mvString(PROPERTY_SPATIAL.getLocalName())).value();
218 final List<MetadataValue<String>> accessRights =
219 mlist(episode.get(PROPERTY_ACCESS_RIGHTS)).map(dc2mvString(PROPERTY_ACCESS_RIGHTS.getLocalName())).value();
220 final List<MetadataValue<String>> licenses =
221 mlist(episode.get(PROPERTY_LICENSE)).map(dc2mvString(PROPERTY_LICENSE.getLocalName())).value();
222
223 return new StaticMetadata() {
224 @Override
225 public Option<String> getId() {
226 return id;
227 }
228
229 @Override
230 public Option<Date[]> getTemporalPeriod() {
231 if (temporalOpt.isSome()) {
232 if (temporalOpt.get() instanceof DCMIPeriod) {
233 DCMIPeriod p = (DCMIPeriod) temporalOpt.get();
234 return option(new Date[] { p.getStart(), p.getEnd() });
235 }
236 }
237 return Option.none();
238 }
239
240 @Override
241 public Option<Date> getTemporalInstant() {
242 if (temporalOpt.isSome()) {
243 if (temporalOpt.get() instanceof Date) {
244 return temporalOpt;
245 }
246 }
247 return Option.none();
248 }
249
250 @Override
251 public Option<Long> getTemporalDuration() {
252 if (temporalOpt.isSome()) {
253 if (temporalOpt.get() instanceof Long) {
254 return temporalOpt;
255 }
256 }
257 return Option.none();
258 }
259
260 @Override
261 public Option<Long> getExtent() {
262 return extent;
263 }
264
265 @Override
266 public Option<String> getLanguage() {
267 return language;
268 }
269
270 @Override
271 public Option<String> getIsPartOf() {
272 return isPartOf;
273 }
274
275 @Override
276 public Option<String> getReplaces() {
277 return replaces;
278 }
279
280 @Override
281 public Option<String> getType() {
282 return type;
283 }
284
285 @Override
286 public Option<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 Function<DublinCoreValue, Object> dc2temporalValueOption() {
357 return new Function<DublinCoreValue, Object>() {
358 @Override
359 public Object apply(DublinCoreValue dcv) {
360 Temporal temporal = EncodingSchemeUtils.decodeTemporal(dcv);
361 if (temporal != null) {
362 return temporal.fold(new Temporal.Match<Object>() {
363 @Override
364 public Object period(DCMIPeriod period) {
365 return period;
366 }
367
368 @Override
369 public Object instant(Date instant) {
370 return instant;
371 }
372
373 @Override
374 public Object duration(long duration) {
375 return duration;
376 }
377 });
378 }
379 return Misc.<Object>chuck(new RuntimeException(dcv
380 + " does not conform to ISO8601 encoding scheme for temporal."));
381 }
382 };
383 }
384
385
386
387
388 private static Function<DublinCoreValue, MetadataValue<String>> dc2mvString(final String name) {
389 return new Function<DublinCoreValue, MetadataValue<String>>() {
390 @Override
391 public MetadataValue<String> apply(DublinCoreValue dcv) {
392 return new MetadataValue<String>(dcv.getValue(), name, dcv.getLanguage());
393 }
394 };
395 }
396
397 private static Predicate<Catalog> flavorPredicate(final MediaPackageElementFlavor flavor) {
398 return new Predicate<Catalog>() {
399 @Override
400 public Boolean apply(Catalog catalog) {
401 return flavor.equals(catalog.getFlavor());
402 }
403 };
404 }
405
406 private Option<DublinCoreCatalog> load(Catalog catalog) {
407 InputStream in = null;
408 try {
409 URI uri = catalog.getURI();
410 if (serializer != null) uri = serializer.decodeURI(uri);
411 in = workspace.read(uri);
412 return some((DublinCoreCatalog) DublinCores.read(in));
413 } catch (Exception e) {
414 logger.warn("Unable to load metadata from catalog '{}'", catalog);
415 return Option.none();
416 } finally {
417 IOUtils.closeQuietly(in);
418 }
419 }
420 }