View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  package org.opencastproject.assetmanager.impl.persistence;
22  
23  import static org.opencastproject.db.Queries.namedQuery;
24  
25  import org.opencastproject.assetmanager.api.Availability;
26  import org.opencastproject.assetmanager.api.Property;
27  import org.opencastproject.assetmanager.api.PropertyId;
28  import org.opencastproject.assetmanager.api.Snapshot;
29  import org.opencastproject.assetmanager.impl.HttpAssetProvider;
30  import org.opencastproject.assetmanager.impl.PartialMediaPackage;
31  import org.opencastproject.assetmanager.impl.VersionImpl;
32  import org.opencastproject.db.DBSession;
33  import org.opencastproject.db.Queries;
34  import org.opencastproject.mediapackage.MediaPackage;
35  import org.opencastproject.mediapackage.MediaPackageElement;
36  
37  import org.apache.commons.lang3.StringUtils;
38  import org.apache.commons.lang3.tuple.Pair;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  import java.util.Collection;
43  import java.util.Collections;
44  import java.util.Date;
45  import java.util.List;
46  import java.util.Optional;
47  import java.util.stream.Collectors;
48  
49  import javax.annotation.ParametersAreNonnullByDefault;
50  import javax.persistence.NoResultException;
51  import javax.persistence.TypedQuery;
52  
53  /**
54   * Data access object.
55   */
56  @ParametersAreNonnullByDefault
57  public class Database {
58    private static final Logger logger = LoggerFactory.getLogger(Database.class);
59  
60    private final DBSession db;
61  
62    private HttpAssetProvider httpAssetProvider;
63  
64    public Database(DBSession db) {
65      this.db = db;
66    }
67  
68    public void setHttpAssetProvider(HttpAssetProvider httpAssetProvider) {
69      this.httpAssetProvider = httpAssetProvider;
70    }
71  
72    /**
73     * Save a property to the database. This is either an insert or an update operation.
74     */
75    public boolean saveProperty(final Property property) {
76      return db.execTx(em -> {
77        final PropertyId pId = property.getId();
78        final String mpId = pId.getMediaPackageId();
79        final String namespace = pId.getNamespace();
80        final String propertyName = pId.getName();
81  
82        // Check if media package exists in oc_assets_snapshot table
83        Long snapshotCount = em.createQuery(
84                "SELECT COUNT(s.id) FROM Snapshot s "
85                    + "WHERE s.mediaPackageId = :mediaPackageId", Long.class)
86            .setParameter("mediaPackageId", mpId)
87            .getSingleResult();
88  
89        if (snapshotCount == 0) {
90          return false; // media package does not exist
91        }
92  
93        // Try to find existing property
94        TypedQuery<PropertyDto> query = em.createQuery(
95            "SELECT p FROM Property p "
96                + "WHERE p.mediaPackageId = :mediaPackageId "
97                + "AND p.namespace = :namespace "
98                + "AND p.propertyName = :propertyName",
99            PropertyDto.class);
100       query.setParameter("mediaPackageId", mpId);
101       query.setParameter("namespace", namespace);
102       query.setParameter("propertyName", propertyName);
103 
104       PropertyDto existing = null;
105       try {
106         existing = query.getSingleResult();
107       } catch (NoResultException e) {
108         // property does not exist, we'll insert
109       }
110 
111       PropertyDto updatedOrNew = existing == null
112           ? PropertyDto.mk(property)
113           : existing.update(property.getValue());
114 
115       namedQuery.persistOrUpdate(updatedOrNew).apply(em);
116       return true;
117     });
118   }
119 
120 
121 
122   /**
123    * Claim a new version for media package <code>mpId</code>.
124    */
125   public VersionImpl claimVersion(final String mpId) {
126     return db.execTx(em -> {
127       final Optional<VersionClaimDto> lastOpt = VersionClaimDto.findLastQuery(mpId).apply(em);
128       if (lastOpt.isPresent()) {
129         final VersionImpl claim = VersionImpl.next(lastOpt.get().getLastClaimed());
130         VersionClaimDto.updateQuery(mpId, claim.value()).apply(em);
131         return claim;
132       } else {
133         final VersionImpl first = VersionImpl.FIRST;
134         em.persist(VersionClaimDto.mk(mpId, first.value()));
135         return first;
136       }
137     });
138   }
139 
140   /**
141    * Save a snapshot and all of its assets.
142    */
143   public SnapshotDto saveSnapshot(
144           final String orgId,
145           final PartialMediaPackage pmp,
146           final Date archivalDate,
147           final VersionImpl version,
148           final Availability availability,
149           final String storageId,
150           final String owner) {
151     final SnapshotDto snapshotDto = SnapshotDto.mk(
152             pmp.getMediaPackage(),
153             version,
154             orgId,
155             archivalDate,
156             availability,
157             storageId,
158             owner);
159     return db.execTx(em -> {
160       // persist snapshot
161       em.persist(snapshotDto);
162       // persist assets
163       for (MediaPackageElement e : pmp.getElements()) {
164         final AssetDto a = AssetDto.mk(
165             e.getIdentifier(),
166             snapshotDto,
167             e.getChecksum().toString(),
168             Optional.ofNullable(e.getMimeType()),
169             storageId,
170             e.getSize());
171         em.persist(a);
172       }
173       return snapshotDto;
174     });
175   }
176 
177   public void setStorageLocation(Snapshot snapshot, final String storageId) {
178     setStorageLocation(
179         VersionImpl.mk(snapshot.getVersion()),
180         snapshot.getMediaPackage().getIdentifier().toString(),
181         storageId
182     );
183   }
184 
185   public void setStorageLocation(final VersionImpl version, final String mpId, final String storageId) {
186     db.execTx(em -> {
187       // Update snapshot
188       namedQuery.update(
189           "Snapshot.updateStorageIdByVersionAndMpId",
190           Pair.of("storageId", storageId),
191           Pair.of("version", version.value()),
192           Pair.of("mediaPackageId", mpId)
193       ).apply(em);
194 
195       // Update asset
196       Optional<SnapshotDtos.Medium> optSnapshot = getSnapshot(version, mpId);
197       if (optSnapshot.isPresent()) {
198         // Update all associated assets
199         namedQuery.update(
200             "Asset.updateStorageIdBySnapshot",
201             Pair.of("storageId", storageId),
202             Pair.of("snapshot", optSnapshot.get().getSnapshotDto())
203         ).apply(em);
204       }
205 
206       return null;
207     });
208   }
209 
210   public void setAssetStorageLocation(final VersionImpl version, final String mpId, final String mpeId,
211           final String storageId) {
212     db.execTx(em -> {
213       Optional<SnapshotDtos.Medium> optSnapshot = getSnapshot(version, mpId);
214       if (optSnapshot.isPresent()) {
215         // Update the asset store id
216         namedQuery.update(
217             "Asset.updateStorageIdBySnapshotAndMpElementId",
218             Pair.of("storageId", storageId),
219             Pair.of("snapshot", optSnapshot.get().getSnapshotDto()),
220             Pair.of("mediaPackageElementId", mpeId)
221         ).apply(em);
222       }
223 
224       return null;
225     });
226   }
227 
228   public int setAvailability(final VersionImpl version, final String mpId, final Availability availability) {
229     return db.execTx(em -> {
230       return namedQuery.update(
231           "Snapshot.updateAvailabilityByVersionAndMpId",
232           Pair.of("availability", availability.name()),
233           Pair.of("version", version.value()),
234           Pair.of("mediaPackageId", mpId)
235       ).apply(em);
236     });
237   }
238 
239   /**
240    * Get an asset. If no version is specified return the latest version.
241    *
242    * @return the asset or none, if no asset can be found
243    */
244   public Optional<AssetDto> getAsset(final VersionImpl version, final String mpId, final String mpeId) {
245     return db.execTx(em -> {
246       return Queries.namedQuery
247           .findOpt(
248               "Asset.findAssetByMpIdMpeIdAndVersion",
249               AssetDto.class,
250               Pair.of("mpId", mpId),
251               Pair.of("mpeId", mpeId),
252               Pair.of("version", version.value()))
253           .apply(em);
254     });
255   }
256 
257   public Optional<SnapshotDtos.Medium> getSnapshot(final VersionImpl version, final String mpId) {
258     return db.execTx(em -> {
259       return namedQuery.findOpt(
260           "Snapshot.findByMpIdAndVersionOrderByVersionDesc",
261             SnapshotDto.class,
262             Pair.of("mpId", mpId),
263             Pair.of("version", version.value()))
264           .apply(em)
265           .map(result -> new SnapshotDtos.Medium(
266               result,
267               Availability.valueOf(result.getAvailability()),
268               result.getStorageId(),
269               result.getOrganizationId(),
270               result.getOwner()
271           ));
272     });
273   }
274 
275 
276   /**
277    * Delete all properties for a given media package identifier
278    *
279    * @param mediaPackageId
280    *          Media package identifier
281    * @return Number of deleted rows
282    */
283   public int deleteProperties(final String mediaPackageId) {
284     return db.execTx(PropertyDto.deleteQuery(mediaPackageId));
285   }
286 
287   /**
288    * Delete all properties for a given media package identifier and namespace.
289    *
290    * @param mediaPackageId
291    *          Media package identifier
292    * @param namespace
293    *          A namespace prefix to use for deletion
294    * @return Number of deleted rows
295    */
296   public int deleteProperties(final String mediaPackageId, final String namespace) {
297     if (StringUtils.isBlank(namespace)) {
298       return db.execTx(PropertyDto.deleteQuery(mediaPackageId));
299     }
300     return db.execTx(PropertyDto.deleteQuery(mediaPackageId, namespace));
301   }
302 
303   /**
304    * Check if any snapshot with the given media package identifier exists.
305    *
306    * @param mediaPackageId
307    *          The media package identifier to check for
308    * @return If a snapshot exists for the given media package
309    */
310   public boolean snapshotExists(final String mediaPackageId) {
311     return db.execTx(SnapshotDto.existsQuery(mediaPackageId));
312   }
313 
314   /**
315    * Check if any snapshot with the given media package identifier exists.
316    *
317    * @param mediaPackageId
318    *          The media package identifier to check for
319    * @param organization
320    *          The organization to filter for
321    * @return If a snapshot exists for the given media package
322    */
323   public boolean snapshotExists(final String mediaPackageId, final String organization) {
324     return db.exec(SnapshotDto.existsQuery(mediaPackageId, organization));
325   }
326 
327   /**
328    * Select all properties for a specific media package.
329    *
330    * @param mediaPackageId
331    *          Media package identifier to check for
332    * @param namespace
333    *          Namespace to limit the search to
334    * @return List of properties
335    */
336   public List<Property> selectProperties(final String mediaPackageId, final String namespace) {
337     return db.exec(PropertyDto.selectQuery(mediaPackageId, namespace));
338   }
339 
340   /**
341    * Count events with snapshots in the asset manager
342    *
343    * @param organization
344    *          An organization to count in
345    * @return Number of events
346    */
347   public long countEvents(final String organization) {
348     return db.exec(SnapshotDto.countEventsQuery(organization));
349   }
350 
351   /**
352    * Count events with snapshots in the asset manager
353    *
354    * @param organization
355    *          An organization to count in
356    * @return Number of events
357    */
358   public long countSnapshots(final String organization) {
359     return db.exec(SnapshotDto.countSnapshotsQuery(organization));
360   }
361 
362   public long countAssets() {
363     return db.exec(AssetDto.countAssetsQuery());
364   }
365 
366   public long countProperties() {
367     return db.exec(PropertyDto.countPropertiesQuery());
368   }
369 
370   public Optional<AssetDto> findAssetByChecksumAndStoreAndOrg(final String checksum, final String storeId,
371       final String orgId) {
372     return db.execTx(em -> {
373       return namedQuery.findSome(
374               "Asset.findByChecksumStorageIdAndOrganizationId",
375               // limit to one result (which one doesn't matter as the checksum, storage and org ID are equal)
376               0,
377               1,
378               AssetDto.class,
379               Pair.of("checksum", checksum),
380               Pair.of("storageId", storeId),
381               Pair.of("orgId", orgId))
382           .apply(em)
383           .stream()
384           .findFirst();
385     });
386   }
387 
388   public Optional<Snapshot> getLatestSnapshot(String mediaPackageId) {
389     return getLatestSnapshot(mediaPackageId, null);
390   }
391 
392   public Optional<Snapshot> getLatestSnapshot(String mediaPackageId, String orgId) {
393     return db.execTx(em -> {
394       Optional<SnapshotDto> snapshotDto = namedQuery.findOpt(
395           "Snapshot.findLatest",
396           SnapshotDto.class,
397           Pair.of("mediaPackageId", mediaPackageId),
398           Pair.of("organizationId", orgId)
399       ).apply(em);
400 
401       if (snapshotDto.isEmpty()) {
402         return Optional.empty();
403       }
404 
405       Snapshot snapshot = snapshotDto.get().toSnapshot();
406       // make sure the delivered media package has valid URIs
407       snapshot = httpAssetProvider.prepareForDelivery(snapshot);
408 
409       return Optional.of(snapshot);
410     });
411   }
412 
413   public Optional<MediaPackage> getMediaPackage(String mediaPackageId) {
414     return getMediaPackage(mediaPackageId, null);
415   }
416   public Optional<MediaPackage> getMediaPackage(String mediaPackageId, String orgId) {
417     return getLatestSnapshot(mediaPackageId, orgId).map(Snapshot::getMediaPackage);
418   }
419 
420   public List<Snapshot> getSnapshots(String mediaPackageId) {
421     return getSnapshots(mediaPackageId, null);
422   }
423 
424   public List<Snapshot> getSnapshots(String mediaPackageId, String orgId) {
425     return getSnapshots(mediaPackageId, orgId, null);
426   }
427 
428   public List<Snapshot> getSnapshots(String mediaPackageId, String orgId, String orderByVersion) {
429     return db.execTx(em -> {
430       String namedQueryName = "ASC".equals(orderByVersion)
431           ? "Snapshot.findOldestVersionFirst" : "Snapshot.findLatestVersionFirst";
432 
433       List<SnapshotDto> snapshotDto = namedQuery.findAll(
434           namedQueryName,
435           SnapshotDto.class,
436           Pair.of("mediaPackageId", mediaPackageId),
437           Pair.of("organizationId", orgId)
438       ).apply(em);
439 
440       return snapshotDtoToSnapshot(snapshotDto);
441     });
442   }
443 
444   public int deleteSnapshots(String mediaPackageId, String orgId) {
445     return db.execTx(em -> {
446       return namedQuery.delete(
447           "Snapshot.delete",
448           Pair.of("mediaPackageId", mediaPackageId),
449           Pair.of("organizationId", orgId)
450       ).apply(em);
451     });
452   }
453 
454   public int deleteAllButLatestSnapshot(String mediaPackageId, String orgId) {
455     return db.execTx(em -> {
456       return namedQuery.delete(
457           "Snapshot.deleteAllButLatest",
458           Pair.of("mediaPackageId", mediaPackageId),
459           Pair.of("organizationId", orgId)
460       ).apply(em);
461     });
462   }
463 
464   public List<Snapshot> getSnapshotsByMpIdAndVersion(String mediaPackageId, Long version, String orgId) {
465     return db.execTx(em -> {
466       List<SnapshotDto> snapshotDto = namedQuery.findAll(
467           "Snapshot.findByMpIdAndVersion",
468           SnapshotDto.class,
469           Pair.of("mediaPackageId", mediaPackageId),
470           Pair.of("version", version),
471           Pair.of("organizationId", orgId)
472       ).apply(em);
473 
474       return snapshotDtoToSnapshot(snapshotDto);
475     });
476   }
477 
478   public List<Snapshot> getSnapshotsByDateOrderByMpId(Date start, Date end, String orgId) {
479     return db.execTx(em -> {
480       List<SnapshotDto> snapshotDto = namedQuery.findAll(
481           "Snapshot.findByDateOrderByMpId",
482           SnapshotDto.class,
483           Pair.of("startDate", start),
484           Pair.of("endDate", end),
485           Pair.of("organizationId", orgId)
486       ).apply(em);
487 
488       return snapshotDtoToSnapshot(snapshotDto);
489     });
490   }
491 
492   public List<Snapshot> getSnapshotsByMpdIdAndDate(String mediaPackageId, Date start, Date end, String orgId) {
493     return getSnapshotsByMpdIdAndDate(mediaPackageId, start, end, orgId, null);
494   }
495 
496   public List<Snapshot> getSnapshotsByMpdIdAndDate(String mediaPackageId, Date start, Date end, String orgId,
497       String orderByVersion) {
498     return db.execTx(em -> {
499       String namedQueryName = "Snapshot.findByMpIdAndDate";
500       if ("ASC".equals(orderByVersion)) {
501         namedQueryName = "Snapshot.findByMpIdAndDateOldestVersionFirst";
502       }
503       if ("DESC".equals(orderByVersion)) {
504         namedQueryName = "Snapshot.findByMpIdAndDateLatestVersionFirst";
505       }
506 
507       List<SnapshotDto> snapshotDto = namedQuery.findAll(
508           namedQueryName,
509           SnapshotDto.class,
510           Pair.of("mediaPackageId", mediaPackageId),
511           Pair.of("startDate", start),
512           Pair.of("endDate", end),
513           Pair.of("organizationId", orgId)
514       ).apply(em);
515 
516       return snapshotDtoToSnapshot(snapshotDto);
517     });
518   }
519 
520   public List<Snapshot> getSnapshotsByNotStorageAndDate(String storageId, Date start, Date end, String orgId) {
521     return db.execTx(em -> {
522       List<SnapshotDto> snapshotDto = namedQuery.findAll(
523           "Snapshot.findByNotStorageAndDate",
524           SnapshotDto.class,
525           Pair.of("storageId", storageId),
526           Pair.of("startDate", start),
527           Pair.of("endDate", end),
528           Pair.of("organizationId", orgId)
529       ).apply(em);
530 
531       return snapshotDtoToSnapshot(snapshotDto);
532     });
533   }
534 
535   public List<Snapshot> getSnapshotsBySeries(String seriesId, String orgId) {
536     return db.execTx(em -> {
537       List<SnapshotDto> snapshotDto = namedQuery.findAll(
538           "Snapshot.findLatestBySeriesId",
539           SnapshotDto.class,
540           Pair.of("seriesId", seriesId),
541           Pair.of("organizationId", orgId)
542       ).apply(em);
543 
544       return snapshotDtoToSnapshot(snapshotDto);
545     });
546   }
547 
548   public List<Snapshot> getLatestSnapshotsByMediaPackageIds(Collection mediaPackageIds, String orgId) {
549     if (mediaPackageIds.isEmpty()) {
550       return Collections.emptyList();
551     }
552     return db.execTx(em -> {
553       List<SnapshotDto> snapshotDto = namedQuery.findAll(
554           "Snapshot.findLatestByMpIds",
555           SnapshotDto.class,
556           Pair.of("mediaPackageIds", mediaPackageIds),
557           Pair.of("organizationId", orgId)
558       ).apply(em);
559 
560       return snapshotDtoToSnapshot(snapshotDto);
561     });
562   }
563 
564   public Optional<Snapshot> getSnapshot(String mediaPackageId, String orgId, Long version) {
565     return db.execTx(em -> {
566       Optional<SnapshotDto> snapshotDto = namedQuery.findOpt(
567           "Snapshot.findByMpIdOrgIdAndVersion",
568           SnapshotDto.class,
569           Pair.of("mediaPackageId", mediaPackageId),
570           Pair.of("organizationId", orgId),
571           Pair.of("version", version)
572       ).apply(em);
573 
574       if (snapshotDto.isEmpty()) {
575         return Optional.empty();
576       }
577 
578       Snapshot snapshot = snapshotDto.get().toSnapshot();
579       // make sure the delivered media package has valid URIs
580       snapshot = httpAssetProvider.prepareForDelivery(snapshot);
581 
582       return Optional.of(snapshot);
583     });
584   }
585 
586   public List<Snapshot> getSnapshotsForIndexRebuild(int offset, int limit) {
587     return db.execTx(em -> {
588       List<SnapshotDto> snapshotDto = namedQuery.findSome(
589           "Snapshot.findForIndexRebuild",
590           offset,
591           limit,
592           SnapshotDto.class
593       ).apply(em);
594 
595       return snapshotDtoToSnapshot(snapshotDto);
596     });
597   }
598 
599   public List<Long> getVersionsByMediaPackage(String mediaPackageId, String orgId) {
600     return db.execTx(em -> {
601       List<Long> versions = namedQuery.findAll(
602           "Snapshot.getSnapshotVersions",
603           Long.class,
604           Pair.of("mediaPackageId", mediaPackageId),
605           Pair.of("organizationId", orgId)
606       ).apply(em);
607 
608       return versions;
609     });
610   }
611 
612   //
613   // Utility
614   //
615 
616   public static <A> A insidePersistenceContextCheck(A a) {
617     if (a != null) {
618       return a;
619     } else {
620       throw new RuntimeException(
621           "Used DTO outside of a persistence context or the DTO has not been assigned an ID yet.");
622     }
623   }
624 
625   private List<Snapshot> snapshotDtoToSnapshot(List<SnapshotDto> snapshotDtos) {
626     return snapshotDtos.stream()
627         .map(s -> s.toSnapshot())
628         .map(s -> httpAssetProvider.prepareForDelivery(s))
629         .collect(Collectors.toList());
630   }
631 }