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.util.data.functions.Misc.chuck;
24  
25  import org.opencastproject.assetmanager.api.Availability;
26  import org.opencastproject.assetmanager.api.Snapshot;
27  import org.opencastproject.assetmanager.impl.SnapshotImpl;
28  import org.opencastproject.assetmanager.impl.VersionImpl;
29  import org.opencastproject.mediapackage.MediaPackage;
30  import org.opencastproject.mediapackage.MediaPackageElement;
31  import org.opencastproject.mediapackage.MediaPackageParser;
32  
33  import org.eclipse.persistence.annotations.CascadeOnDelete;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  import java.util.Arrays;
38  import java.util.Date;
39  import java.util.Set;
40  import java.util.function.Function;
41  
42  import javax.persistence.CascadeType;
43  import javax.persistence.Column;
44  import javax.persistence.Entity;
45  import javax.persistence.EntityManager;
46  import javax.persistence.FetchType;
47  import javax.persistence.GeneratedValue;
48  import javax.persistence.GenerationType;
49  import javax.persistence.Id;
50  import javax.persistence.Index;
51  import javax.persistence.Lob;
52  import javax.persistence.NamedQueries;
53  import javax.persistence.NamedQuery;
54  import javax.persistence.OneToMany;
55  import javax.persistence.Table;
56  import javax.persistence.TableGenerator;
57  import javax.persistence.Temporal;
58  import javax.persistence.TemporalType;
59  import javax.persistence.TypedQuery;
60  import javax.persistence.UniqueConstraint;
61  
62  /** JPA DTO. */
63  @Entity(name = "Snapshot")
64  @Table(name = "oc_assets_snapshot", indexes = {
65      @Index(name = "IX_oc_assets_snapshot_archival_date", columnList = ("archival_date")),
66      @Index(name = "IX_oc_assets_snapshot_mediapackage_id", columnList = ("mediapackage_id")),
67      @Index(name = "IX_oc_assets_snapshot_organization_id", columnList = ("organization_id")),
68      @Index(name = "IX_oc_assets_snapshot_owner", columnList = ("owner")),
69      @Index(name = "IX_oc_assets_snapshot_series", columnList = ("series_id, version"))
70      }, uniqueConstraints = {
71      @UniqueConstraint(columnNames = {"mediapackage_id", "version"}) })
72  @NamedQueries({
73          @NamedQuery(name = "Snapshot.countOrgEvents", query = "select count(distinct s.mediaPackageId) from Snapshot s "
74                  + "where s.organizationId = :organizationId"),
75          @NamedQuery(name = "Snapshot.countEvents", query = "select count(distinct s.mediaPackageId) from Snapshot s"),
76          @NamedQuery(name = "Snapshot.countByMediaPackage", query = "select count(s) from Snapshot s "
77                  + "where s.mediaPackageId = :mediaPackageId"),
78          @NamedQuery(name = "Snapshot.countByMediaPackageAndOrg", query = "select count(s) from Snapshot s "
79                  + "where s.mediaPackageId = :mediaPackageId and s.organizationId = :organizationId"),
80          @NamedQuery(
81              name = "Snapshot.findByMpIdAndVersionOrderByVersionDesc",
82              query = "SELECT s FROM Snapshot s "
83                  + "WHERE s.mediaPackageId = :mpId "
84                  + "AND s.version = :version "
85                  + "ORDER BY s.version DESC"
86          ),
87          @NamedQuery(
88              name = "Snapshot.findLatest",
89              query = "SELECT s FROM Snapshot s "
90                  + "WHERE s.mediaPackageId = :mediaPackageId "
91                  + "AND (:organizationId IS NULL OR s.organizationId = :organizationId) "
92                  + "AND s.version = ( "
93                  + "  SELECT MAX(s2.version) FROM Snapshot s2 WHERE s2.mediaPackageId = s.mediaPackageId "
94                  + ")"
95          ),
96          @NamedQuery(
97              name = "Snapshot.findLatestVersionFirst",
98              query = "SELECT s FROM Snapshot s "
99                  + "WHERE s.mediaPackageId = :mediaPackageId "
100                 + "AND (:organizationId IS NULL OR s.organizationId = :organizationId) "
101                 + "ORDER BY s.version DESC"
102         ),
103         @NamedQuery(
104             name = "Snapshot.findOldestVersionFirst",
105             query = "SELECT s FROM Snapshot s "
106                 + "WHERE s.mediaPackageId = :mediaPackageId "
107                 + "AND (:organizationId IS NULL OR s.organizationId = :organizationId) "
108                 + "ORDER BY s.version ASC"
109         ),
110         @NamedQuery(
111             name = "Snapshot.findByMpIdAndVersion",
112             query = "SELECT s FROM Snapshot s "
113                 + "WHERE s.mediaPackageId = :mediaPackageId "
114                 + "AND (:organizationId IS NULL OR s.organizationId = :organizationId) "
115                 + "AND s.version = :version"
116         ),
117         @NamedQuery(
118             name = "Snapshot.findByDateOrderByMpId",
119             query = "SELECT s FROM Snapshot s "
120                 + "WHERE s.archivalDate BETWEEN :startDate AND :endDate "
121                 + "AND (:organizationId IS NULL OR s.organizationId = :organizationId) "
122                 + "ORDER BY s.mediaPackageId ASC"
123         ),
124         @NamedQuery(
125             name = "Snapshot.findByMpIdAndDate",
126             query = "SELECT s FROM Snapshot s "
127                 + "WHERE s.mediaPackageId = :mediaPackageId "
128                 + "AND s.archivalDate BETWEEN :startDate AND :endDate "
129                 + "AND (:organizationId IS NULL OR s.organizationId = :organizationId) "
130         ),
131         @NamedQuery(
132             name = "Snapshot.findByMpIdAndDateLatestVersionFirst",
133             query = "SELECT s FROM Snapshot s "
134                 + "WHERE s.mediaPackageId = :mediaPackageId "
135                 + "AND s.archivalDate BETWEEN :startDate AND :endDate "
136                 + "AND (:organizationId IS NULL OR s.organizationId = :organizationId) "
137                 + "ORDER BY s.version DESC"
138         ),
139         @NamedQuery(
140             name = "Snapshot.findByMpIdAndDateOldestVersionFirst",
141             query = "SELECT s FROM Snapshot s "
142                 + "WHERE s.mediaPackageId = :mediaPackageId "
143                 + "AND s.archivalDate BETWEEN :startDate AND :endDate "
144                 + "AND (:organizationId IS NULL OR s.organizationId = :organizationId) "
145                 + "ORDER BY s.version ASC"
146         ),
147         @NamedQuery(
148             name = "Snapshot.findByNotStorageAndDate",
149             query = "SELECT s FROM Snapshot s "
150                 + "WHERE s.storageId != :storageId "
151                 + "AND s.archivalDate BETWEEN :startDate AND :endDate "
152                 + "AND (:organizationId IS NULL OR s.organizationId = :organizationId)"
153         ),
154         @NamedQuery(
155             name = "Snapshot.findForIndexRebuild",
156             query = "SELECT s FROM Snapshot s "
157                 + "WHERE s.version = ( "
158                 + "  SELECT MAX(s2.version) FROM Snapshot s2 "
159                 + "  WHERE s2.mediaPackageId = s.mediaPackageId "
160                 + ") "
161                 + "ORDER BY s.mediaPackageId DESC "
162         ),
163         @NamedQuery(
164             name = "Snapshot.delete",
165             query = "DELETE FROM Snapshot s "
166                 + "WHERE s.mediaPackageId = :mediaPackageId "
167                 + "AND (:organizationId IS NULL OR s.organizationId = :organizationId) "
168         ),
169         @NamedQuery(
170             name = "Snapshot.deleteAllButLatest",
171             query = "DELETE FROM Snapshot s "
172                 + "WHERE s.mediaPackageId = :mediaPackageId "
173                 + "AND (:organizationId IS NULL OR s.organizationId = :organizationId) "
174                 + "AND s.version < ( "
175                 + "  SELECT MAX(s2.version) FROM Snapshot s2 WHERE s2.mediaPackageId = :mediaPackageId "
176                 + ") "
177         ),
178         @NamedQuery(
179             name = "Snapshot.findLatestBySeriesId",
180             query = "SELECT s FROM Snapshot s "
181                 + "WHERE s.seriesId = :seriesId "
182                 + "AND (:organizationId IS NULL OR s.organizationId = :organizationId) "
183                 + "AND s.version = ( "
184                 + "  SELECT MAX(s2.version) FROM Snapshot s2 WHERE s2.mediaPackageId = s.mediaPackageId "
185                 + ")"
186         ),
187         @NamedQuery(
188             name = "Snapshot.findLatestByMpIds",
189             query = "SELECT s FROM Snapshot s "
190                 + "WHERE s.mediaPackageId IN :mediaPackageIds "
191                 + "AND (:organizationId IS NULL OR s.organizationId = :organizationId) "
192                 + "AND s.version = ( "
193                 + "  SELECT MAX(s2.version) FROM Snapshot s2 WHERE s2.mediaPackageId = s.mediaPackageId "
194                 + ") "
195         ),
196         @NamedQuery(
197             name = "Snapshot.findByMpIdOrgIdAndVersion",
198             query = "SELECT s FROM Snapshot s "
199                 + "WHERE s.mediaPackageId = :mediaPackageId "
200                 + "AND s.organizationId = :organizationId "
201                 + "AND s.version = :version "
202         ),
203         @NamedQuery(
204             name = "Snapshot.countSnapshots",
205             query = "SELECT COUNT(s) FROM Snapshot s "
206                 + "WHERE (:organizationId IS NULL OR s.organizationId = :organizationId)"
207         ),
208         @NamedQuery(
209             name = "Snapshot.updateStorageIdByVersionAndMpId",
210             query = "UPDATE Snapshot s SET s.storageId = :storageId "
211                 + "WHERE s.version = :version "
212                 + "AND s.mediaPackageId = :mediaPackageId"
213         ),
214         @NamedQuery(
215             name = "Snapshot.updateAvailabilityByVersionAndMpId",
216             query = "UPDATE Snapshot s SET s.availability = :availability "
217                 + "WHERE s.version = :version "
218                 + "AND s.mediaPackageId = :mediaPackageId"
219         ),
220         @NamedQuery(
221           name = "Snapshot.getSnapshotVersions",
222           query = "SELECT s.version FROM Snapshot s "
223                 + "WHERE s.mediaPackageId = :mediaPackageId "
224                 + "AND (:organizationId IS NULL OR s.organizationId = :organizationId) "
225                 + "ORDER BY s.version ASC"
226         ),
227 })
228 // Maintain own generator to support database migrations from Archive to AssetManager
229 // The generator's initial value has to be set after the data migration.
230 // Otherwise duplicate key errors will most likely happen.
231 @TableGenerator(name = "seq_oc_assets_snapshot", initialValue = 0, allocationSize = 50)
232 public class SnapshotDto {
233   private static final Logger logger = LoggerFactory.getLogger(SnapshotDto.class);
234 
235   @Id
236   @Column(name = "id")
237   @GeneratedValue(strategy = GenerationType.TABLE, generator = "seq_oc_assets_snapshot")
238   private Long id;
239 
240   @Column(name = "mediapackage_id", length = 128, nullable = false)
241   private String mediaPackageId;
242 
243   @Column(name = "version", nullable = false)
244   private Long version;
245 
246   @Column(name = "series_id", length = 128)
247   private String seriesId;
248 
249   @Column(name = "organization_id", length = 128, nullable = false)
250   private String organizationId;
251 
252   @Column(name = "archival_date", nullable = false)
253   @Temporal(TemporalType.TIMESTAMP)
254   private Date archivalDate;
255 
256   @Column(name = "availability", nullable = false, length = 32)
257   private String availability;
258 
259   @Column(name = "storage_id", nullable = false, length = 256)
260   private String storageId;
261 
262   @Column(name = "owner", nullable = false, length = 256)
263   private String owner;
264 
265   @Lob
266   @Column(name = "mediapackage_xml", length = 65535, nullable = false)
267   private String mediaPackageXml;
268 
269   @CascadeOnDelete
270   @OneToMany(targetEntity = AssetDto.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "snapshot")
271   private Set<AssetDto> assets;
272 
273   public static SnapshotDto mk(
274           MediaPackage mediaPackage,
275           VersionImpl version,
276           String organization,
277           Date archivalDate,
278           Availability availability,
279           String storageId,
280           String owner) {
281     try {
282       final SnapshotDto dto = new SnapshotDto();
283       dto.mediaPackageId = mediaPackage.getIdentifier().toString();
284       dto.version = version.value();
285       dto.seriesId = mediaPackage.getSeries();
286       dto.organizationId = organization;
287       dto.archivalDate = archivalDate;
288       dto.mediaPackageXml = MediaPackageParser.getAsXml(mediaPackage);
289       dto.availability = availability.name();
290       dto.storageId = storageId;
291       dto.owner = owner;
292       return dto;
293     } catch (Exception e) {
294       return chuck(e);
295     }
296   }
297 
298   public static SnapshotDto mk(Snapshot snapshot) {
299     try {
300       return mk(snapshot.getMediaPackage(),
301               VersionImpl.mk(Long.parseLong(snapshot.getVersion().toString())),
302               snapshot.getOrganizationId(),
303               snapshot.getArchivalDate(),
304               snapshot.getAvailability(),
305               snapshot.getStorageId(),
306               snapshot.getOwner());
307     } catch (Exception e) {
308       return chuck(e);
309     }
310   }
311 
312   public Long getId() {
313     return Database.insidePersistenceContextCheck(id);
314   }
315 
316   public VersionImpl getVersion() {
317     return Conversions.toVersion(version);
318   }
319 
320   public String getMediaPackageId() {
321     return mediaPackageId;
322   }
323 
324   public String getStorageId() {
325     return storageId;
326   }
327 
328   public String getOrganizationId() {
329     return organizationId;
330   }
331 
332   public String getAvailability() {
333     return availability;
334   }
335 
336   public String getOwner() {
337     return owner;
338   }
339 
340   void setAvailability(Availability a) {
341     this.availability = a.name();
342   }
343 
344   void setStorageId(String id) {
345     this.storageId = id;
346   }
347 
348   public boolean addAsset(AssetDto asset) {
349     return this.assets.add(asset);
350   }
351 
352   public boolean removeAsset(AssetDto asset) {
353     return this.assets.remove(asset);
354   }
355 
356   public Snapshot toSnapshot() {
357     MediaPackage mediaPackage = Conversions.toMediaPackage(mediaPackageXml);
358     // ensure elements are tagged `archive`
359     for (MediaPackageElement element: mediaPackage.getElements()) {
360       if (!Arrays.asList(element.getTags()).contains("archive")) {
361         logger.debug("Adding additional tag `archive` to element {} retrieved from asset manager", element);
362         element.addTag("archive");
363       }
364     }
365     return new SnapshotImpl(
366             id,
367             Conversions.toVersion(version),
368             organizationId,
369             archivalDate,
370             Availability.valueOf(availability),
371             storageId,
372             owner,
373             mediaPackage);
374   }
375 
376   /**
377    * Check if any snapshot with the given media package exists.
378    *
379    * @param mediaPackageId
380    *          The media package identifier to check for
381    * @return If a snapshot exists for the given media package
382    */
383   public static Function<EntityManager, Boolean> existsQuery(final String mediaPackageId) {
384     return existsQuery(mediaPackageId, null);
385   }
386 
387   /**
388    * Check if any snapshot with the given media package exists.
389    *
390    * @param mediaPackageId
391    *          The media package identifier to check for
392    * @param organization
393    *          An organization to limit the check for
394    * @return If a snapshot exists for the given media package
395    */
396   public static Function<EntityManager, Boolean> existsQuery(final String mediaPackageId, final String organization) {
397     return em -> {
398       TypedQuery<Long> query;
399       if (organization == null) {
400         query = em.createNamedQuery("Snapshot.countByMediaPackage", Long.class)
401             .setParameter("mediaPackageId", mediaPackageId);
402       } else {
403         query = em.createNamedQuery("Snapshot.countByMediaPackageAndOrg", Long.class)
404             .setParameter("mediaPackageId", mediaPackageId)
405             .setParameter("organizationId", organization);
406       }
407       logger.debug("Executing query {}", query);
408       return query.getSingleResult() > 0;
409     };
410   }
411 
412   /**
413    * Count events with snapshots in the asset manager
414    *
415    * @param organization
416    *          An organization to count in
417    * @return Number of events
418    */
419   public static Function<EntityManager, Long> countEventsQuery(final String organization) {
420     return em -> {
421       TypedQuery<Long> query;
422       if (null != organization) {
423         query = em.createNamedQuery("Snapshot.countOrgEvents", Long.class)
424             .setParameter("organizationId", organization);
425       } else {
426         query = em.createNamedQuery("Snapshot.countEvents", Long.class);
427       }
428       logger.debug("Executing query {}", query);
429       return query.getSingleResult();
430     };
431   }
432 
433   /**
434    * Count events with snapshots in the asset manager
435    *
436    * @param organization
437    *          An organization to count in
438    * @return Number of events
439    */
440   public static Function<EntityManager, Long> countSnapshotsQuery(final String organization) {
441     return em -> {
442       TypedQuery<Long> query;
443       query = em.createNamedQuery("Snapshot.countSnapshots", Long.class)
444           .setParameter("organizationId", organization);
445       logger.debug("Executing query {}", query);
446       return query.getSingleResult();
447     };
448   }
449 }