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