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.PartialMediaPackage;
30  import org.opencastproject.assetmanager.impl.VersionImpl;
31  import org.opencastproject.assetmanager.impl.persistence.AssetDtos.Full;
32  import org.opencastproject.assetmanager.impl.persistence.AssetDtos.Medium;
33  import org.opencastproject.db.DBSession;
34  import org.opencastproject.mediapackage.MediaPackageElement;
35  import org.opencastproject.util.data.Function;
36  
37  import com.entwinemedia.fn.data.Opt;
38  import com.mysema.query.Tuple;
39  import com.mysema.query.jpa.EclipseLinkTemplates;
40  import com.mysema.query.jpa.JPQLTemplates;
41  import com.mysema.query.jpa.impl.JPADeleteClause;
42  import com.mysema.query.jpa.impl.JPAQuery;
43  import com.mysema.query.jpa.impl.JPAQueryFactory;
44  import com.mysema.query.jpa.impl.JPAUpdateClause;
45  import com.mysema.query.types.expr.BooleanExpression;
46  
47  import org.apache.commons.lang3.StringUtils;
48  import org.slf4j.Logger;
49  import org.slf4j.LoggerFactory;
50  
51  import java.util.Date;
52  import java.util.List;
53  import java.util.Optional;
54  
55  import javax.annotation.ParametersAreNonnullByDefault;
56  
57  /**
58   * Data access object.
59   */
60  @ParametersAreNonnullByDefault
61  public class Database implements EntityPaths {
62    private static final Logger logger = LoggerFactory.getLogger(Database.class);
63  
64    public static final JPQLTemplates TEMPLATES = EclipseLinkTemplates.DEFAULT;
65  
66    private final DBSession db;
67  
68    public Database(DBSession db) {
69      this.db = db;
70    }
71  
72    /**
73     * Run a Queryldsl query inside a persistence context/transaction.
74     *
75     * @param q the query function to run
76     */
77    public <A> A run(final Function<JPAQueryFactory, A> q) {
78      return db.execTx(em -> {
79        return q.apply(new JPAQueryFactory(TEMPLATES, () -> em));
80      });
81    }
82  
83    public void logQuery(JPAQuery q) {
84      logger.debug("\n---\nQUERY\n{}\n---", q);
85    }
86  
87    public void logDelete(String queryName, JPADeleteClause q) {
88      logger.debug("\n---\nDELETE {}\n{}\n---", queryName, q);
89    }
90  
91    /**
92     * Save a property to the database. This is either an insert or an update operation.
93     */
94    public boolean saveProperty(final Property property) {
95      return db.execTx(em -> {
96        final PropertyId pId = property.getId();
97        // check the existence of both the media package and the property in one query
98        //
99        // either the property matches or it does not exist <- left outer join
100       final BooleanExpression eitherMatchOrNull =
101           Q_PROPERTY.namespace.eq(pId.getNamespace())
102               .and(Q_PROPERTY.propertyName.eq(pId.getName())).or(Q_PROPERTY.namespace.isNull());
103       final Tuple result = new JPAQuery(em, TEMPLATES)
104           .from(Q_SNAPSHOT)
105           .leftJoin(Q_PROPERTY).on(Q_SNAPSHOT.mediaPackageId.eq(Q_PROPERTY.mediaPackageId).and(eitherMatchOrNull))
106           .where(Q_SNAPSHOT.mediaPackageId.eq(pId.getMediaPackageId()))
107           // only one result is interesting, no need to fetch all versions of the media package
108           .singleResult(Q_SNAPSHOT.id, Q_PROPERTY);
109       if (result != null) {
110         // media package exists, now check if the property exists
111         final PropertyDto exists = result.get(Q_PROPERTY);
112         namedQuery
113             .persistOrUpdate(exists == null
114                 ? PropertyDto.mk(property)
115                 : exists.update(property.getValue()))
116             .apply(em);
117         return true;
118       } else {
119         // media package does not exist
120         return false;
121       }
122     });
123   }
124 
125   /**
126    * Claim a new version for media package <code>mpId</code>.
127    */
128   public VersionImpl claimVersion(final String mpId) {
129     return db.execTx(em -> {
130       final Optional<VersionClaimDto> lastOpt = VersionClaimDto.findLastQuery(mpId).apply(em);
131       if (lastOpt.isPresent()) {
132         final VersionImpl claim = VersionImpl.next(lastOpt.get().getLastClaimed());
133         VersionClaimDto.updateQuery(mpId, claim.value()).apply(em);
134         return claim;
135       } else {
136         final VersionImpl first = VersionImpl.FIRST;
137         em.persist(VersionClaimDto.mk(mpId, first.value()));
138         return first;
139       }
140     });
141   }
142 
143   /**
144    * Save a snapshot and all of its assets.
145    */
146   public SnapshotDto saveSnapshot(
147           final String orgId,
148           final PartialMediaPackage pmp,
149           final Date archivalDate,
150           final VersionImpl version,
151           final Availability availability,
152           final String storageId,
153           final String owner) {
154     final SnapshotDto snapshotDto = SnapshotDto.mk(
155             pmp.getMediaPackage(),
156             version,
157             orgId,
158             archivalDate,
159             availability,
160             storageId,
161             owner);
162     return db.execTx(em -> {
163       // persist snapshot
164       em.persist(snapshotDto);
165       // persist assets
166       for (MediaPackageElement e : pmp.getElements()) {
167         final AssetDto a = AssetDto.mk(
168             e.getIdentifier(),
169             snapshotDto,
170             e.getChecksum().toString(),
171             Optional.ofNullable(e.getMimeType()),
172             storageId,
173             e.getSize());
174         em.persist(a);
175       }
176       return snapshotDto;
177     });
178   }
179 
180   public void setStorageLocation(Snapshot snapshot, final String storageId) {
181     setStorageLocation(
182         VersionImpl.mk(snapshot.getVersion()),
183         snapshot.getMediaPackage().getIdentifier().toString(),
184         storageId
185     );
186   }
187 
188   public void setStorageLocation(final VersionImpl version, final String mpId, final String storageId) {
189     db.execTx(em -> {
190       final QSnapshotDto q = QSnapshotDto.snapshotDto;
191       final QAssetDto a = QAssetDto.assetDto;
192       // Update the snapshot
193       new JPAUpdateClause(em, q, TEMPLATES)
194           .where(q.version.eq(version.value()).and(q.mediaPackageId.eq(mpId)))
195           .set(q.storageId, storageId)
196           .execute();
197       // Get the snapshot (to get its database ID)
198       Optional<SnapshotDtos.Medium> s = getSnapshot(version, mpId);
199       // Update the assets
200       new JPAUpdateClause(em, a, TEMPLATES)
201           .where(a.snapshot.id.eq(s.get().getSnapshotDto().getId()))
202           .set(a.storageId, storageId)
203           .execute();
204       return null;
205     });
206   }
207 
208   public void setAssetStorageLocation(final VersionImpl version, final String mpId, final String mpeId,
209           final String storageId) {
210     db.execTx(em -> {
211       final QAssetDto a = QAssetDto.assetDto;
212       Optional<SnapshotDtos.Medium> s = getSnapshot(version, mpId);
213       // Update the asset store id
214       new JPAUpdateClause(em, a, TEMPLATES)
215           .where(a.snapshot.id.eq(s.get().getSnapshotDto().getId()).and(a.mediaPackageElementId.eq(mpeId)))
216           .set(a.storageId, storageId).execute();
217       return null;
218     });
219   }
220 
221   public void setAvailability(final VersionImpl version, final String mpId, final Availability availability) {
222     db.execTx(em -> {
223       final QSnapshotDto q = QSnapshotDto.snapshotDto;
224       new JPAUpdateClause(em, q, TEMPLATES)
225           .where(q.version.eq(version.value()).and(q.mediaPackageId.eq(mpId)))
226           .set(q.availability, availability.name())
227           .execute();
228       return null;
229     });
230   }
231 
232   /**
233    * Get an asset. If no version is specified return the latest version.
234    *
235    * @return the asset or none, if no asset can be found
236    */
237   public Optional<AssetDtos.Medium> getAsset(final VersionImpl version, final String mpId, final String mpeId) {
238     return db.execTx(em -> {
239       final QAssetDto assetDto = QAssetDto.assetDto;
240       final Tuple result = AssetDtos.baseJoin(em)
241           .where(assetDto.snapshot.mediaPackageId.eq(mpId)
242               .and(assetDto.mediaPackageElementId.eq(mpeId))
243               .and(assetDto.snapshot.version.eq(version.value())))
244           // if no version has been specified make sure to get the latest by ordering
245           .orderBy(assetDto.snapshot.version.desc())
246           .uniqueResult(Medium.select);
247       var dtoOpt = Opt.nul(result).map(AssetDtos.Medium.fromTuple);
248       return dtoOpt.isSome() ? Optional.of(dtoOpt.get()) : Optional.empty();
249     });
250   }
251 
252   public Optional<SnapshotDtos.Medium> getSnapshot(final VersionImpl version, final String mpId) {
253     return db.execTx(em -> {
254       final QSnapshotDto snapshotDto = QSnapshotDto.snapshotDto;
255       final Tuple result = SnapshotDtos.baseQuery(em)
256           .where(snapshotDto.mediaPackageId.eq(mpId)
257               .and(snapshotDto.version.eq(version.value())))
258           // if no version has been specified make sure to get the latest by ordering
259           .orderBy(snapshotDto.version.desc())
260           .uniqueResult(SnapshotDtos.Medium.select);
261       var dtoOpt = Opt.nul(result).map(SnapshotDtos.Medium.fromTuple);
262       return dtoOpt.isSome() ? Optional.of(dtoOpt.get()) : Optional.empty();
263     });
264   }
265 
266   public Optional<AssetDtos.Full> findAssetByChecksum(final String checksum) {
267     return db.execTx(em -> {
268       final Tuple result = AssetDtos.baseJoin(em)
269           .where(QAssetDto.assetDto.checksum.eq(checksum))
270           .singleResult(Full.select);
271       var dtoOpt = Opt.nul(result).map(Full.fromTuple);
272       return dtoOpt.isSome() ? Optional.of(dtoOpt.get()) : Optional.empty();
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   public Optional<AssetDtos.Full> findAssetByChecksumAndStoreAndOrg(final String checksum, final String storeId,
352       final String orgId) {
353     return db.execTx(em -> {
354       final Tuple result = AssetDtos.baseJoin(em)
355           .where(QAssetDto.assetDto.checksum.eq(checksum)
356               .and(QAssetDto.assetDto.storageId.eq(storeId))
357               .and(QAssetDto.assetDto.snapshot.organizationId.eq(orgId)))
358           .singleResult(Full.select);
359       var dtoOpt = Opt.nul(result).map(Full.fromTuple);
360       return dtoOpt.isSome() ? Optional.of(dtoOpt.get()) : Optional.empty();
361     });
362   }
363 
364   //
365   // Utility
366   //
367 
368   public static <A> A insidePersistenceContextCheck(A a) {
369     if (a != null) {
370       return a;
371     } else {
372       throw new RuntimeException(
373           "Used DTO outside of a persistence context or the DTO has not been assigned an ID yet.");
374     }
375   }
376 }