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.search.impl.persistence;
23
24 import static org.opencastproject.db.Queries.namedQuery;
25 import static org.opencastproject.security.api.Permissions.Action.CONTRIBUTE;
26 import static org.opencastproject.security.api.Permissions.Action.READ;
27 import static org.opencastproject.security.api.Permissions.Action.WRITE;
28 import static org.opencastproject.security.api.SecurityConstants.GLOBAL_CAPTURE_AGENT_ROLE;
29
30 import org.opencastproject.db.DBSession;
31 import org.opencastproject.db.DBSessionFactory;
32 import org.opencastproject.mediapackage.MediaPackage;
33 import org.opencastproject.mediapackage.MediaPackageException;
34 import org.opencastproject.mediapackage.MediaPackageParser;
35 import org.opencastproject.security.api.AccessControlList;
36 import org.opencastproject.security.api.AccessControlParser;
37 import org.opencastproject.security.api.AccessControlParsingException;
38 import org.opencastproject.security.api.AccessControlUtil;
39 import org.opencastproject.security.api.Organization;
40 import org.opencastproject.security.api.SecurityService;
41 import org.opencastproject.security.api.UnauthorizedException;
42 import org.opencastproject.security.api.User;
43 import org.opencastproject.util.NotFoundException;
44 import org.opencastproject.util.data.Tuple;
45
46 import org.apache.commons.lang3.StringUtils;
47 import org.apache.commons.lang3.tuple.Pair;
48 import org.osgi.service.component.ComponentContext;
49 import org.osgi.service.component.annotations.Activate;
50 import org.osgi.service.component.annotations.Component;
51 import org.osgi.service.component.annotations.Reference;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 import java.io.IOException;
56 import java.util.ArrayList;
57 import java.util.Arrays;
58 import java.util.Collection;
59 import java.util.Date;
60 import java.util.List;
61 import java.util.Optional;
62 import java.util.function.Function;
63 import java.util.stream.Stream;
64
65 import javax.persistence.EntityManager;
66 import javax.persistence.EntityManagerFactory;
67 import javax.persistence.TypedQuery;
68
69
70
71
72 @Component(
73 immediate = true,
74 service = SearchServiceDatabase.class,
75 property = {
76 "service.description=Search Service Persistence"
77 }
78 )
79 public class SearchServiceDatabaseImpl implements SearchServiceDatabase {
80
81
82 public static final String PERSISTENCE_UNIT = "org.opencastproject.search.impl.persistence";
83
84
85 private static final Logger logger = LoggerFactory.getLogger(SearchServiceDatabaseImpl.class);
86
87
88 protected EntityManagerFactory emf;
89
90 protected DBSessionFactory dbSessionFactory;
91
92 protected DBSession db;
93
94
95 protected SecurityService securityService;
96
97
98 @Reference(target = "(osgi.unit.name=org.opencastproject.search.impl.persistence)")
99 public void setEntityManagerFactory(EntityManagerFactory emf) {
100 this.emf = emf;
101 }
102
103 @Reference
104 public void setDBSessionFactory(DBSessionFactory dbSessionFactory) {
105 this.dbSessionFactory = dbSessionFactory;
106 }
107
108
109
110
111
112
113
114 @Activate
115 public void activate(ComponentContext cc) throws SearchServiceDatabaseException {
116 logger.info("Activating persistence manager for search service");
117 db = dbSessionFactory.createSession(emf);
118 this.populateSeriesData();
119 }
120
121
122
123
124
125
126
127 @Reference
128 public void setSecurityService(SecurityService securityService) {
129 this.securityService = securityService;
130 }
131
132 private void populateSeriesData() throws SearchServiceDatabaseException {
133 try {
134 db.execTxChecked(em -> {
135 TypedQuery<SearchEntity> q = em.createNamedQuery("Search.getNoSeries", SearchEntity.class);
136 List<SearchEntity> seriesList = q.getResultList();
137 for (SearchEntity series : seriesList) {
138 String mpSeriesId = MediaPackageParser.getFromXml(series.getMediaPackageXML()).getSeries();
139 if (StringUtils.isNotBlank(mpSeriesId) && !mpSeriesId.equals(series.getSeriesId())) {
140 logger.info("Fixing missing series ID for episode {}, series is {}", series.getMediaPackageId(),
141 mpSeriesId);
142 series.setSeriesId(mpSeriesId);
143 em.merge(series);
144 }
145 }
146 });
147 } catch (Exception e) {
148 logger.error("Could not update media package: {}", e.getMessage());
149 throw new SearchServiceDatabaseException(e);
150 }
151 }
152
153
154
155
156
157
158 @Override
159 public void deleteMediaPackage(String mediaPackageId, Date deletionDate) throws SearchServiceDatabaseException,
160 NotFoundException, UnauthorizedException {
161 try {
162 db.execTxChecked(em -> {
163 Optional<SearchEntity> searchEntity = getSearchEntityQuery(mediaPackageId).apply(em);
164 if (searchEntity.isEmpty()) {
165 throw new NotFoundException("No media package with id=" + mediaPackageId + " exists");
166 }
167
168
169 User currentUser = securityService.getUser();
170 Organization currentOrg = securityService.getOrganization();
171 MediaPackage searchMp = MediaPackageParser.getFromXml(searchEntity.get().getMediaPackageXML());
172 String accessControlXml = searchEntity.get().getAccessControl();
173
174
175 if (!(searchMp.isLive() && currentUser.hasRole(GLOBAL_CAPTURE_AGENT_ROLE)) && accessControlXml != null) {
176 AccessControlList acl = AccessControlParser.parseAcl(accessControlXml);
177 if (!AccessControlUtil.isAuthorized(acl, currentUser, currentOrg, WRITE.toString(), mediaPackageId)) {
178 throw new UnauthorizedException(
179 currentUser + " is not authorized to delete media package " + mediaPackageId);
180 }
181 }
182
183 searchEntity.get().setDeletionDate(deletionDate);
184 searchEntity.get().setModificationDate(deletionDate);
185 em.merge(searchEntity.get());
186 });
187 } catch (NotFoundException | UnauthorizedException e) {
188 throw e;
189 } catch (Exception e) {
190 logger.error("Could not delete episode {}: {}", mediaPackageId, e.getMessage());
191 throw new SearchServiceDatabaseException(e);
192 }
193 }
194
195
196
197
198
199
200 @Override
201 public int countMediaPackages() throws SearchServiceDatabaseException {
202 try {
203 return db.exec(namedQuery.find("Search.getCount", Long.class)).intValue();
204 } catch (Exception e) {
205 logger.error("Could not find number of mediapackages", e);
206 throw new SearchServiceDatabaseException(e);
207 }
208 }
209
210
211
212
213
214
215 @Override
216 public Stream<Tuple<MediaPackage, String>> getAllMediaPackages(int pagesize, int offset)
217 throws SearchServiceDatabaseException {
218 List<SearchEntity> searchEntities;
219 try {
220 int firstResult = pagesize * offset;
221 searchEntities = db.exec(namedQuery.findSome("Search.findAll", firstResult, pagesize, SearchEntity.class));
222 } catch (Exception e) {
223 logger.error("Could not retrieve all episodes: {}", e.getMessage());
224 throw new SearchServiceDatabaseException(e);
225 }
226
227 try {
228 return searchEntities.stream()
229 .map(entity -> {
230 try {
231 MediaPackage mediaPackage = MediaPackageParser.getFromXml(entity.getMediaPackageXML());
232 return Tuple.tuple(mediaPackage, entity.getOrganization().getId());
233 } catch (Exception e) {
234 logger.error("Could not parse series entity: {}", e.getMessage());
235 throw new RuntimeException(e);
236 }
237 });
238 } catch (Exception e) {
239 logger.error("Could not parse series entity: {}", e.getMessage());
240 throw new SearchServiceDatabaseException(e);
241 }
242 }
243
244
245
246
247
248
249 @Override
250 public AccessControlList getAccessControlList(String mediaPackageId) throws NotFoundException,
251 SearchServiceDatabaseException {
252 try {
253 Optional<SearchEntity> entity = db.exec(getSearchEntityQuery(mediaPackageId));
254 if (entity.isEmpty()) {
255 throw new NotFoundException("Could not found media package with ID " + mediaPackageId);
256 }
257 if (entity.get().getAccessControl() == null) {
258 return null;
259 } else {
260 return AccessControlParser.parseAcl(entity.get().getAccessControl());
261 }
262 } catch (NotFoundException e) {
263 throw e;
264 } catch (Exception e) {
265 logger.error("Could not retrieve ACL {}", mediaPackageId, e);
266 throw new SearchServiceDatabaseException(e);
267 }
268 }
269
270
271
272
273
274
275 @Override
276 public Collection<Pair<String, AccessControlList>> getAccessControlLists(final String seriesId, String ... excludeIds)
277 throws SearchServiceDatabaseException {
278 List<String> excludes = Arrays.asList(excludeIds);
279 List<Pair<String,AccessControlList>> accessControlLists = new ArrayList<>();
280 try {
281 List<SearchEntity> result = db.exec(namedQuery.findAll(
282 "Search.findBySeriesId",
283 SearchEntity.class,
284 Pair.of("seriesId", seriesId)
285 ));
286 for (SearchEntity entity: result) {
287 if (entity.getAccessControl() != null && !excludes.contains(entity.getMediaPackageId())) {
288 accessControlLists.add(Pair.of(
289 entity.getMediaPackageId(),
290 AccessControlParser.parseAcl(entity.getAccessControl()))
291 );
292 }
293 }
294 } catch (IOException | AccessControlParsingException e) {
295 throw new SearchServiceDatabaseException(e);
296 }
297 return accessControlLists;
298 }
299
300
301
302
303
304
305 public Collection<Pair<Organization, MediaPackage>> getSeries(final String seriesId)
306 throws SearchServiceDatabaseException {
307 List<Pair<Organization, MediaPackage>> episodes = new ArrayList<>();
308 EntityManager em = emf.createEntityManager();
309 TypedQuery<SearchEntity> q = em.createNamedQuery("Search.findBySeriesId", SearchEntity.class)
310 .setParameter("seriesId", seriesId);
311 try {
312 for (SearchEntity entity: q.getResultList()) {
313 if (entity.getMediaPackageXML() != null) {
314 episodes.add(Pair.of(
315 entity.getOrganization(),
316 MediaPackageParser.getFromXml(entity.getMediaPackageXML())));
317 }
318 }
319 } catch (MediaPackageException e) {
320 throw new SearchServiceDatabaseException(e);
321 } finally {
322 em.close();
323 }
324 return episodes;
325 }
326
327
328
329
330
331
332
333 @Override
334 public void storeMediaPackage(MediaPackage mediaPackage, AccessControlList acl, Date now)
335 throws SearchServiceDatabaseException, UnauthorizedException {
336 String mediaPackageXML = MediaPackageParser.getAsXml(mediaPackage);
337 String mediaPackageId = mediaPackage.getIdentifier().toString();
338 try {
339 db.execTxChecked(em -> {
340 Optional<SearchEntity> entity = getSearchEntityQuery(mediaPackageId).apply(em);
341 if (entity.isEmpty()) {
342
343 SearchEntity searchEntity = new SearchEntity();
344 searchEntity.setOrganization(securityService.getOrganization());
345 searchEntity.setMediaPackageId(mediaPackageId);
346 searchEntity.setMediaPackageXML(mediaPackageXML);
347 searchEntity.setAccessControl(AccessControlParser.toXml(acl));
348 searchEntity.setModificationDate(now);
349 searchEntity.setSeriesId(mediaPackage.getSeries());
350 em.persist(searchEntity);
351 } else {
352
353
354 String accessControlXml = entity.get().getAccessControl();
355 if (accessControlXml != null && entity.get().getDeletionDate() == null) {
356 AccessControlList accessList = AccessControlParser.parseAcl(accessControlXml);
357 User currentUser = securityService.getUser();
358 Organization currentOrg = securityService.getOrganization();
359 if (!AccessControlUtil.isAuthorized(accessList, currentUser, currentOrg, WRITE.toString(),
360 mediaPackageId)) {
361 throw new UnauthorizedException(currentUser + " is not authorized to update media package "
362 + mediaPackageId);
363 }
364 }
365 entity.get().setOrganization(securityService.getOrganization());
366 entity.get().setMediaPackageId(mediaPackageId);
367 entity.get().setMediaPackageXML(mediaPackageXML);
368 entity.get().setAccessControl(AccessControlParser.toXml(acl));
369 entity.get().setModificationDate(now);
370 entity.get().setDeletionDate(null);
371 entity.get().setSeriesId(mediaPackage.getSeries());
372 em.merge(entity.get());
373 }
374 });
375 } catch (UnauthorizedException e) {
376 throw e;
377 } catch (Exception e) {
378 logger.error("Could not update media package: {}", e.getMessage());
379 throw new SearchServiceDatabaseException(e);
380 }
381 }
382
383
384
385
386
387
388 @Override
389 public MediaPackage getMediaPackage(String mediaPackageId)
390 throws NotFoundException, SearchServiceDatabaseException, UnauthorizedException {
391 try {
392 return db.execTxChecked(em -> {
393 Optional<SearchEntity> episodeEntity = getSearchEntityQuery(mediaPackageId).apply(em);
394 if (episodeEntity.isEmpty() || episodeEntity.get().getDeletionDate() != null) {
395 throw new NotFoundException("No episode with id=" + mediaPackageId + " exists");
396 }
397
398 String accessControlXml = episodeEntity.get().getAccessControl();
399 if (accessControlXml != null) {
400 AccessControlList acl = AccessControlParser.parseAcl(accessControlXml);
401 User currentUser = securityService.getUser();
402 Organization currentOrg = securityService.getOrganization();
403
404 if (!AccessControlUtil.isAuthorized(acl, currentUser, currentOrg, READ.toString(), mediaPackageId)
405 && !AccessControlUtil.isAuthorized(acl, currentUser, currentOrg, CONTRIBUTE.toString(),
406 mediaPackageId)
407 && !AccessControlUtil.isAuthorized(acl, currentUser, currentOrg, WRITE.toString(), mediaPackageId)) {
408 throw new UnauthorizedException(currentUser + " is not authorized to see episode " + mediaPackageId);
409 }
410 }
411 return MediaPackageParser.getFromXml(episodeEntity.get().getMediaPackageXML());
412 });
413 } catch (NotFoundException | UnauthorizedException e) {
414 throw e;
415 } catch (Exception e) {
416 logger.error("Could not get episode {} from database: {} ", mediaPackageId, e.getMessage());
417 throw new SearchServiceDatabaseException(e);
418 }
419 }
420
421
422
423
424
425
426 @Override
427 public Date getModificationDate(String mediaPackageId) throws NotFoundException, SearchServiceDatabaseException {
428 try {
429 return db.execTxChecked(em -> {
430 Optional<SearchEntity> searchEntity = getSearchEntityQuery(mediaPackageId).apply(em);
431 if (searchEntity.isEmpty()) {
432 throw new NotFoundException("No media package with id=" + mediaPackageId + " exists");
433 }
434
435 String accessControlXml = searchEntity.get().getAccessControl();
436 if (accessControlXml != null) {
437 AccessControlList acl = AccessControlParser.parseAcl(accessControlXml);
438 User currentUser = securityService.getUser();
439 Organization currentOrg = securityService.getOrganization();
440 if (!AccessControlUtil.isAuthorized(acl, currentUser, currentOrg, READ.toString(), mediaPackageId)) {
441 throw new UnauthorizedException(
442 currentUser + " is not authorized to read media package " + mediaPackageId);
443 }
444 }
445 return searchEntity.get().getModificationDate();
446 });
447 } catch (NotFoundException e) {
448 throw e;
449 } catch (Exception e) {
450 logger.error("Could not get modification date {}: {}", mediaPackageId, e.getMessage());
451 throw new SearchServiceDatabaseException(e);
452 }
453 }
454
455
456
457
458
459
460 @Override
461 public Date getDeletionDate(String mediaPackageId) throws NotFoundException, SearchServiceDatabaseException {
462 try {
463 return db.execTxChecked(em -> {
464 Optional<SearchEntity> searchEntity = getSearchEntityQuery(mediaPackageId).apply(em);
465 if (searchEntity.isEmpty()) {
466 throw new NotFoundException("No media package with id=" + mediaPackageId + " exists");
467 }
468
469 String accessControlXml = searchEntity.get().getAccessControl();
470 if (accessControlXml != null) {
471 AccessControlList acl = AccessControlParser.parseAcl(accessControlXml);
472 User currentUser = securityService.getUser();
473 Organization currentOrg = securityService.getOrganization();
474 if (!AccessControlUtil.isAuthorized(acl, currentUser, currentOrg, READ.toString(), mediaPackageId)) {
475 throw new UnauthorizedException(
476 currentUser + " is not authorized to read media package " + mediaPackageId);
477 }
478 }
479 return searchEntity.get().getDeletionDate();
480 });
481 } catch (NotFoundException e) {
482 throw e;
483 } catch (Exception e) {
484 logger.error("Could not get deletion date {}: {}", mediaPackageId, e.getMessage());
485 throw new SearchServiceDatabaseException(e);
486 }
487 }
488
489
490
491
492
493
494 public boolean isAvailable(String mediaPackageId) throws SearchServiceDatabaseException {
495 try {
496 return db.execTxChecked(em -> {
497 Optional<SearchEntity> searchEntity = getSearchEntityQuery(mediaPackageId).apply(em);
498 return searchEntity.stream().anyMatch(entity -> entity.getDeletionDate() == null);
499 });
500 } catch (Exception e) {
501 logger.error("Error while checking if mediapackage {} exists in database: {}", mediaPackageId, e.getMessage());
502 throw new SearchServiceDatabaseException(e);
503 }
504 }
505
506
507
508
509
510
511 @Override
512 public String getOrganizationId(String mediaPackageId) throws NotFoundException, SearchServiceDatabaseException {
513 try {
514 return db.execTxChecked(em -> {
515 Optional<SearchEntity> searchEntity = getSearchEntityQuery(mediaPackageId).apply(em);
516 if (searchEntity.isEmpty()) {
517 throw new NotFoundException("No media package with id=" + mediaPackageId + " exists");
518 }
519
520 String accessControlXml = searchEntity.get().getAccessControl();
521 if (accessControlXml != null) {
522 AccessControlList acl = AccessControlParser.parseAcl(accessControlXml);
523 User currentUser = securityService.getUser();
524 Organization currentOrg = securityService.getOrganization();
525 if (!AccessControlUtil.isAuthorized(acl, currentUser, currentOrg, READ.toString(), mediaPackageId)) {
526 throw new UnauthorizedException(
527 currentUser + " is not authorized to read media package " + mediaPackageId);
528 }
529 }
530 return searchEntity.get().getOrganization().getId();
531 });
532 } catch (NotFoundException e) {
533 throw e;
534 } catch (Exception e) {
535 logger.error("Could not get deletion date {}: {}", mediaPackageId, e.getMessage());
536 throw new SearchServiceDatabaseException(e);
537 }
538 }
539
540
541
542
543
544
545
546
547 private Function<EntityManager, Optional<SearchEntity>> getSearchEntityQuery(String id) {
548 return namedQuery.findOpt(
549 "Search.findById",
550 SearchEntity.class,
551 Pair.of("mediaPackageId", id)
552 );
553 }
554 }