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       );
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<AssetDtos.Medium> getAsset(final VersionImpl version, final String mpId, final String mpeId) {
245     return db.execTx(em -> {
246       return Queries.namedQuery
247           .findOpt(
248               "Asset.findMediumByMpIdMpeIdAndVersion",
249               Object[].class,
250               Pair.of("mpId", mpId),
251               Pair.of("mpeId", mpeId),
252               Pair.of("version", version.value()))
253           .apply(em)
254           .map(result -> {
255             AssetDto assetDto = (AssetDto) result[0];
256             String availability = (String) result[1];
257             String organizationId = (String) result[2];
258             return new AssetDtos.Medium(assetDto, Availability.valueOf(availability), organizationId);
259           });
260     });
261   }
262 
263   public Optional<SnapshotDtos.Medium> getSnapshot(final VersionImpl version, final String mpId) {
264     return db.execTx(em -> {
265       return namedQuery.findOpt(
266           "Snapshot.findByMpIdAndVersionOrderByVersionDesc",
267             SnapshotDto.class,
268             Pair.of("mpId", mpId),
269             Pair.of("version", version.value()))
270           .apply(em)
271           .map(result -> new SnapshotDtos.Medium(
272               result,
273               Availability.valueOf(result.getAvailability()),
274               result.getStorageId(),
275               result.getOrganizationId(),
276               result.getOwner()
277           ));
278     });
279   }
280 
281 
282   /**
283    * Delete all properties for a given media package identifier
284    *
285    * @param mediaPackageId
286    *          Media package identifier
287    * @return Number of deleted rows
288    */
289   public int deleteProperties(final String mediaPackageId) {
290     return db.execTx(PropertyDto.deleteQuery(mediaPackageId));
291   }
292 
293   /**
294    * Delete all properties for a given media package identifier and namespace.
295    *
296    * @param mediaPackageId
297    *          Media package identifier
298    * @param namespace
299    *          A namespace prefix to use for deletion
300    * @return Number of deleted rows
301    */
302   public int deleteProperties(final String mediaPackageId, final String namespace) {
303     if (StringUtils.isBlank(namespace)) {
304       return db.execTx(PropertyDto.deleteQuery(mediaPackageId));
305     }
306     return db.execTx(PropertyDto.deleteQuery(mediaPackageId, namespace));
307   }
308 
309   /**
310    * Check if any snapshot with the given media package identifier exists.
311    *
312    * @param mediaPackageId
313    *          The media package identifier to check for
314    * @return If a snapshot exists for the given media package
315    */
316   public boolean snapshotExists(final String mediaPackageId) {
317     return db.execTx(SnapshotDto.existsQuery(mediaPackageId));
318   }
319 
320   /**
321    * Check if any snapshot with the given media package identifier exists.
322    *
323    * @param mediaPackageId
324    *          The media package identifier to check for
325    * @param organization
326    *          The organization to filter for
327    * @return If a snapshot exists for the given media package
328    */
329   public boolean snapshotExists(final String mediaPackageId, final String organization) {
330     return db.exec(SnapshotDto.existsQuery(mediaPackageId, organization));
331   }
332 
333   /**
334    * Select all properties for a specific media package.
335    *
336    * @param mediaPackageId
337    *          Media package identifier to check for
338    * @param namespace
339    *          Namespace to limit the search to
340    * @return List of properties
341    */
342   public List<Property> selectProperties(final String mediaPackageId, final String namespace) {
343     return db.exec(PropertyDto.selectQuery(mediaPackageId, namespace));
344   }
345 
346   /**
347    * Count events with snapshots in the asset manager
348    *
349    * @param organization
350    *          An organization to count in
351    * @return Number of events
352    */
353   public long countEvents(final String organization) {
354     return db.exec(SnapshotDto.countEventsQuery(organization));
355   }
356 
357   /**
358    * Count events with snapshots in the asset manager
359    *
360    * @param organization
361    *          An organization to count in
362    * @return Number of events
363    */
364   public long countSnapshots(final String organization) {
365     return db.exec(SnapshotDto.countSnapshotsQuery(organization));
366   }
367 
368   public long countAssets() {
369     return db.exec(AssetDto.countAssetsQuery());
370   }
371 
372   public long countProperties() {
373     return db.exec(PropertyDto.countPropertiesQuery());
374   }
375 
376   public Optional<AssetDtos.Full> findAssetByChecksumAndStoreAndOrg(final String checksum, final String storeId,
377       final String orgId) {
378     return db.execTx(em -> {
379       return namedQuery.findOpt(
380               "Asset.findByChecksumStorageIdAndOrganizationId",
381               AssetDto.class,
382               Pair.of("checksum", checksum),
383               Pair.of("storageId", storeId),
384               Pair.of("orgId", orgId))
385           .apply(em)
386           .map(result -> {
387             SnapshotDto snapshot = result.getSnapshot();
388             return new AssetDtos.Full(
389                 result,
390                 Availability.valueOf(snapshot.getAvailability()),
391                 snapshot.getStorageId(),
392                 snapshot.getOrganizationId(),
393                 snapshot.getOwner(),
394                 snapshot.getMediaPackageId(),
395                 snapshot.getVersion()
396             );
397           });
398     });
399   }
400 
401   public Optional<Snapshot> getLatestSnapshot(String mediaPackageId) {
402     return getLatestSnapshot(mediaPackageId, null);
403   }
404 
405   public Optional<Snapshot> getLatestSnapshot(String mediaPackageId, String orgId) {
406     return db.execTx(em -> {
407       Optional<SnapshotDto> snapshotDto = namedQuery.findOpt(
408           "Snapshot.findLatest",
409           SnapshotDto.class,
410           Pair.of("mediaPackageId", mediaPackageId),
411           Pair.of("organizationId", orgId)
412       ).apply(em);
413 
414       if (snapshotDto.isEmpty()) {
415         return Optional.empty();
416       }
417 
418       Snapshot snapshot = snapshotDto.get().toSnapshot();
419       // make sure the delivered media package has valid URIs
420       snapshot = httpAssetProvider.prepareForDelivery(snapshot);
421 
422       return Optional.of(snapshot);
423     });
424   }
425 
426   public Optional<MediaPackage> getMediaPackage(String mediaPackageId) {
427     return getMediaPackage(mediaPackageId, null);
428   }
429   public Optional<MediaPackage> getMediaPackage(String mediaPackageId, String orgId) {
430     return getLatestSnapshot(mediaPackageId, orgId).map(Snapshot::getMediaPackage);
431   }
432 
433   public List<Snapshot> getSnapshots(String mediaPackageId) {
434     return getSnapshots(mediaPackageId, null);
435   }
436 
437   public List<Snapshot> getSnapshots(String mediaPackageId, String orgId) {
438     return getSnapshots(mediaPackageId, orgId, null);
439   }
440 
441   public List<Snapshot> getSnapshots(String mediaPackageId, String orgId, String orderByVersion) {
442     return db.execTx(em -> {
443       String namedQueryName = "ASC".equals(orderByVersion)
444           ? "Snapshot.findOldestVersionFirst" : "Snapshot.findLatestVersionFirst";
445 
446       List<SnapshotDto> snapshotDto = namedQuery.findAll(
447           namedQueryName,
448           SnapshotDto.class,
449           Pair.of("mediaPackageId", mediaPackageId),
450           Pair.of("organizationId", orgId)
451       ).apply(em);
452 
453       return snapshotDtoToSnapshot(snapshotDto);
454     });
455   }
456 
457   public int deleteSnapshots(String mediaPackageId, String orgId) {
458     return db.execTx(em -> {
459       return namedQuery.delete(
460           "Snapshot.delete",
461           Pair.of("mediaPackageId", mediaPackageId),
462           Pair.of("organizationId", orgId)
463       ).apply(em);
464     });
465   }
466 
467   public int deleteAllButLatestSnapshot(String mediaPackageId, String orgId) {
468     return db.execTx(em -> {
469       return namedQuery.delete(
470           "Snapshot.deleteAllButLatest",
471           Pair.of("mediaPackageId", mediaPackageId),
472           Pair.of("organizationId", orgId)
473       ).apply(em);
474     });
475   }
476 
477   public List<Snapshot> getSnapshotsByMpIdAndVersion(String mediaPackageId, Long version, String orgId) {
478     return db.execTx(em -> {
479       List<SnapshotDto> snapshotDto = namedQuery.findAll(
480           "Snapshot.findByMpIdAndVersion",
481           SnapshotDto.class,
482           Pair.of("mediaPackageId", mediaPackageId),
483           Pair.of("version", version),
484           Pair.of("organizationId", orgId)
485       ).apply(em);
486 
487       return snapshotDtoToSnapshot(snapshotDto);
488     });
489   }
490 
491   public List<Snapshot> getSnapshotsByDateOrderByMpId(Date start, Date end, String orgId) {
492     return db.execTx(em -> {
493       List<SnapshotDto> snapshotDto = namedQuery.findAll(
494           "Snapshot.findByDateOrderByMpId",
495           SnapshotDto.class,
496           Pair.of("startDate", start),
497           Pair.of("endDate", end),
498           Pair.of("organizationId", orgId)
499       ).apply(em);
500 
501       return snapshotDtoToSnapshot(snapshotDto);
502     });
503   }
504 
505   public List<Snapshot> getSnapshotsByMpdIdAndDate(String mediaPackageId, Date start, Date end, String orgId) {
506     return getSnapshotsByMpdIdAndDate(mediaPackageId, start, end, orgId, null);
507   }
508 
509   public List<Snapshot> getSnapshotsByMpdIdAndDate(String mediaPackageId, Date start, Date end, String orgId,
510       String orderByVersion) {
511     return db.execTx(em -> {
512       String namedQueryName = "Snapshot.findByMpIdAndDate";
513       if ("ASC".equals(orderByVersion)) {
514         namedQueryName = "Snapshot.findByMpIdAndDateOldestVersionFirst";
515       }
516       if ("DESC".equals(orderByVersion)) {
517         namedQueryName = "Snapshot.findByMpIdAndDateLatestVersionFirst";
518       }
519 
520       List<SnapshotDto> snapshotDto = namedQuery.findAll(
521           namedQueryName,
522           SnapshotDto.class,
523           Pair.of("mediaPackageId", mediaPackageId),
524           Pair.of("startDate", start),
525           Pair.of("endDate", end),
526           Pair.of("organizationId", orgId)
527       ).apply(em);
528 
529       return snapshotDtoToSnapshot(snapshotDto);
530     });
531   }
532 
533   public List<Snapshot> getSnapshotsByNotStorageAndDate(String storageId, Date start, Date end, String orgId) {
534     return db.execTx(em -> {
535       List<SnapshotDto> snapshotDto = namedQuery.findAll(
536           "Snapshot.findByNotStorageAndDate",
537           SnapshotDto.class,
538           Pair.of("storageId", storageId),
539           Pair.of("startDate", start),
540           Pair.of("endDate", end),
541           Pair.of("organizationId", orgId)
542       ).apply(em);
543 
544       return snapshotDtoToSnapshot(snapshotDto);
545     });
546   }
547 
548   public List<Snapshot> getSnapshotsBySeries(String seriesId, String orgId) {
549     return db.execTx(em -> {
550       List<SnapshotDto> snapshotDto = namedQuery.findAll(
551           "Snapshot.findLatestBySeriesId",
552           SnapshotDto.class,
553           Pair.of("seriesId", seriesId),
554           Pair.of("organizationId", orgId)
555       ).apply(em);
556 
557       return snapshotDtoToSnapshot(snapshotDto);
558     });
559   }
560 
561   public List<Snapshot> getLatestSnapshotsByMediaPackageIds(Collection mediaPackageIds, String orgId) {
562     if (mediaPackageIds.isEmpty()) {
563       return Collections.emptyList();
564     }
565     return db.execTx(em -> {
566       List<SnapshotDto> snapshotDto = namedQuery.findAll(
567           "Snapshot.findLatestByMpIds",
568           SnapshotDto.class,
569           Pair.of("mediaPackageIds", mediaPackageIds),
570           Pair.of("organizationId", orgId)
571       ).apply(em);
572 
573       return snapshotDtoToSnapshot(snapshotDto);
574     });
575   }
576 
577   public Optional<Snapshot> getSnapshot(String mediaPackageId, String orgId, Long version) {
578     return db.execTx(em -> {
579       Optional<SnapshotDto> snapshotDto = namedQuery.findOpt(
580           "Snapshot.findByMpIdOrgIdAndVersion",
581           SnapshotDto.class,
582           Pair.of("mediaPackageId", mediaPackageId),
583           Pair.of("organizationId", orgId),
584           Pair.of("version", version)
585       ).apply(em);
586 
587       if (snapshotDto.isEmpty()) {
588         return Optional.empty();
589       }
590 
591       Snapshot snapshot = snapshotDto.get().toSnapshot();
592       // make sure the delivered media package has valid URIs
593       snapshot = httpAssetProvider.prepareForDelivery(snapshot);
594 
595       return Optional.of(snapshot);
596     });
597   }
598 
599   public List<Snapshot> getSnapshotsForIndexRebuild(int offset, int limit) {
600     return db.execTx(em -> {
601       List<SnapshotDto> snapshotDto = namedQuery.findSome(
602           "Snapshot.findForIndexRebuild",
603           offset,
604           limit,
605           SnapshotDto.class
606       ).apply(em);
607 
608       return snapshotDtoToSnapshot(snapshotDto);
609     });
610   }
611 
612   public List<Long> getVersionsByMediaPackage(String mediaPackageId, String orgId) {
613     return db.execTx(em -> {
614       List<Long> versions = namedQuery.findAll(
615           "Snapshot.getSnapshotVersions",
616           Long.class,
617           Pair.of("mediaPackageId", mediaPackageId),
618           Pair.of("organizationId", orgId)
619       ).apply(em);
620 
621       return versions;
622     });
623   }
624 
625   //
626   // Utility
627   //
628 
629   public static <A> A insidePersistenceContextCheck(A a) {
630     if (a != null) {
631       return a;
632     } else {
633       throw new RuntimeException(
634           "Used DTO outside of a persistence context or the DTO has not been assigned an ID yet.");
635     }
636   }
637 
638   private List<Snapshot> snapshotDtoToSnapshot(List<SnapshotDto> snapshotDtos) {
639     return snapshotDtos.stream()
640         .map(s -> s.toSnapshot())
641         .map(s -> httpAssetProvider.prepareForDelivery(s))
642         .collect(Collectors.toList());
643   }
644 }