/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License
* at:
*
* http://opensource.org/licenses/ecl2.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
*/
package org.opencastproject.assetmanager.impl.persistence;
import static java.lang.String.format;
import org.opencastproject.assetmanager.api.Availability;
import org.opencastproject.assetmanager.api.Property;
import org.opencastproject.assetmanager.api.PropertyId;
import org.opencastproject.assetmanager.impl.PartialMediaPackage;
import org.opencastproject.assetmanager.impl.VersionImpl;
import org.opencastproject.assetmanager.impl.persistence.AssetDtos.Full;
import org.opencastproject.assetmanager.impl.persistence.AssetDtos.Medium;
import org.opencastproject.mediapackage.MediaPackageElement;
import org.opencastproject.util.persistencefn.PersistenceEnv;
import org.opencastproject.util.persistencefn.PersistenceUtil;
import org.opencastproject.util.persistencefn.PersistenceUtil.DatabaseVendor;
import org.opencastproject.util.persistencefn.Queries;
import com.entwinemedia.fn.Fn;
import com.entwinemedia.fn.Fx;
import com.entwinemedia.fn.data.Opt;
import com.mysema.query.Tuple;
import com.mysema.query.jpa.EclipseLinkTemplates;
import com.mysema.query.jpa.JPQLTemplates;
import com.mysema.query.jpa.impl.JPADeleteClause;
import com.mysema.query.jpa.impl.JPAQuery;
import com.mysema.query.jpa.impl.JPAQueryFactory;
import com.mysema.query.jpa.impl.JPAUpdateClause;
import com.mysema.query.types.expr.BooleanExpression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.DatabaseMetaData;
import java.util.Date;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.inject.Provider;
import javax.persistence.EntityManager;
/**
* Data access object.
*/
@ParametersAreNonnullByDefault
public class Database implements EntityPaths {
private static final Logger logger = LoggerFactory.getLogger(Database.class);
public static final JPQLTemplates TEMPLATES = EclipseLinkTemplates.DEFAULT;
private final PersistenceEnv penv;
public Database(PersistenceEnv penv) {
this.penv = penv;
}
/**
* Run a Queryldsl query inside a persistence context/transaction.
*
* @param q the query function to run
*/
public <A> A run(final Fn<JPAQueryFactory, A> q) {
return penv.tx(new Fn<EntityManager, A>() {
@Override public A apply(final EntityManager em) {
return q.apply(new JPAQueryFactory(TEMPLATES, new Provider<EntityManager>() {
@Override public EntityManager get() {
return em;
}
}));
}
});
}
public <A> A runSql(final Sql<A> sql) {
return penv.tx(new Fn<EntityManager, A>() {
@Override public A apply(EntityManager em) {
for (DatabaseMetaData md : PersistenceUtil.getDatabaseMetadata(em)) {
final DatabaseVendor vendor = PersistenceUtil.getVendor(md);
switch (vendor) {
case H2:
return sql.h2(em);
case MYSQL:
return sql.mysql(em);
case POSTGRES:
return sql.postgres(em);
default:
throw new UnsupportedOperationException("Unsupported database vendor " + vendor);
}
}
logger.warn("Cannot determine database vendor, trying H2");
return sql.h2(em);
}
});
}
public interface Sql<A> {
A h2(EntityManager em);
A mysql(EntityManager em);
A postgres(EntityManager em);
}
public void logQuery(JPAQuery q) {
logger.debug(format("\n---\nQUERY\n%s\n---", q.toString()));
}
public void logDelete(String queryName, JPADeleteClause q) {
logger.debug(format("\n---\nDELETE %s\n%s\n---", queryName, q.toString()));
}
/**
* Save a property to the database. This is either an insert or an update operation.
*/
public boolean saveProperty(final Property property) {
return penv.tx(new Fn<EntityManager, Boolean>() {
@Override public Boolean apply(EntityManager em) {
final PropertyId pId = property.getId();
// check the existence of both the media package and the property in one query
//
// either the property matches or it does not exist <- left outer join
final BooleanExpression eitherMatchOrNull =
Q_PROPERTY.namespace.eq(pId.getNamespace())
.and(Q_PROPERTY.propertyName.eq(pId.getName())).or(Q_PROPERTY.namespace.isNull());
final Tuple result = new JPAQuery(em, TEMPLATES)
.from(Q_SNAPSHOT)
.leftJoin(Q_PROPERTY).on(Q_SNAPSHOT.mediaPackageId.eq(Q_PROPERTY.mediaPackageId).and(eitherMatchOrNull))
.where(Q_SNAPSHOT.mediaPackageId.eq(pId.getMediaPackageId()))
// only one result is interesting, no need to fetch all versions of the media package
.singleResult(Q_SNAPSHOT.id, Q_PROPERTY);
if (result != null) {
// media package exists, now check if the property exists
final PropertyDto exists = result.get(Q_PROPERTY);
Queries.persistOrUpdate(em, exists == null
? PropertyDto.mk(property)
: exists.update(property.getValue()));
return true;
} else {
// media package does not exist
return false;
}
}
});
}
/**
* Claim a new version for media package <code>mpId</code>.
*/
public VersionImpl claimVersion(final String mpId) {
return penv.tx(new Fn<EntityManager, VersionImpl>() {
@Override public VersionImpl apply(final EntityManager em) {
final Opt<VersionClaimDto> lastOpt = VersionClaimDto.findLast(em, mpId);
if (lastOpt.isSome()) {
final VersionImpl claim = VersionImpl.next(lastOpt.get().getLastClaimed());
VersionClaimDto.update(em, mpId, claim.value());
return claim;
} else {
final VersionImpl first = VersionImpl.FIRST;
em.persist(VersionClaimDto.mk(mpId, first.value()));
return first;
}
}
});
}
/**
* Save a snapshot and all of its assets.
*/
public SnapshotDto saveSnapshot(
final String orgId,
final PartialMediaPackage pmp,
final Date archivalDate,
final VersionImpl version,
final Availability availability,
final String owner) {
final SnapshotDto snapshotDto = SnapshotDto.mk(
pmp.getMediaPackage(),
version,
orgId,
archivalDate,
availability,
owner);
return penv.tx(new Fn<EntityManager, SnapshotDto>() {
@Override public SnapshotDto apply(EntityManager em) {
// persist snapshot
em.persist(snapshotDto);
// persist assets
for (MediaPackageElement e : pmp.getElements()) {
final AssetDto a = AssetDto.mk(
e.getIdentifier(),
snapshotDto.getId(),
e.getChecksum().toString(),
Opt.nul(e.getMimeType()),
e.getSize());
em.persist(a);
}
return snapshotDto;
}
});
}
public void setAvailability(final VersionImpl version, final String mpId, final Availability availability) {
penv.tx(new Fx<EntityManager>() {
@Override public void apply(EntityManager em) {
final QSnapshotDto q = QSnapshotDto.snapshotDto;
new JPAUpdateClause(em, q, TEMPLATES)
.where(q.version.eq(version.value()).and(q.mediaPackageId.eq(mpId)))
.set(q.availability, availability.name())
.execute();
}
}.toFn());
}
/**
* Get an asset. If no version is specified return the latest version.
*
* @return the asset or none, if no asset can be found
*/
public Opt<AssetDtos.Medium> getAsset(final VersionImpl version, final String mpId, final String mpeId) {
return penv.tx(new Fn<EntityManager, Opt<AssetDtos.Medium>>() {
@Override public Opt<AssetDtos.Medium> apply(EntityManager em) {
final QAssetDto assetDto = QAssetDto.assetDto;
final QSnapshotDto snapshotDto = QSnapshotDto.snapshotDto;
final Tuple result = AssetDtos.baseJoin(em)
.where(snapshotDto.mediaPackageId.eq(mpId)
.and(assetDto.mediaPackageElementId.eq(mpeId))
.and(snapshotDto.version.eq(version.value())))
// if no version has been specified make sure to get the latest by ordering
.orderBy(snapshotDto.version.desc())
.uniqueResult(Medium.select);
return Opt.nul(result).map(Medium.fromTuple);
}
});
}
public Opt<AssetDtos.Full> findAssetByChecksum(final String checksum) {
return penv.tx(new Fn<EntityManager, Opt<AssetDtos.Full>>() {
@Override public Opt<AssetDtos.Full> apply(EntityManager em) {
final Tuple result = AssetDtos.baseJoin(em).where(QAssetDto.assetDto.checksum.eq(checksum)).singleResult(Full.select);
return Opt.nul(result).map(Full.fromTuple);
}
});
}
//
// Utility
//
public static <A> A insidePersistenceContextCheck(A a) {
if (a != null) {
return a;
} else {
throw new RuntimeException("Used DTO outside of a persistence context or the DTO has not been assigned an ID yet.");
}
}
}