/**
* 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;
import static com.entwinemedia.fn.Stream.$;
import static com.entwinemedia.fn.fns.Booleans.eq;
import static java.lang.String.format;
import static org.junit.Assert.assertEquals;
import org.opencastproject.assetmanager.api.AssetManager;
import org.opencastproject.assetmanager.api.Snapshot;
import org.opencastproject.assetmanager.api.Version;
import org.opencastproject.assetmanager.api.fn.Snapshots;
import org.opencastproject.assetmanager.api.query.AQueryBuilder;
import org.opencastproject.assetmanager.api.query.PropertyField;
import org.opencastproject.assetmanager.api.query.PropertySchema;
import org.opencastproject.assetmanager.impl.persistence.Database;
import org.opencastproject.assetmanager.impl.storage.AssetStore;
import org.opencastproject.assetmanager.impl.storage.AssetStoreException;
import org.opencastproject.assetmanager.impl.storage.DeletionSelector;
import org.opencastproject.assetmanager.impl.storage.Source;
import org.opencastproject.assetmanager.impl.storage.StoragePath;
import org.opencastproject.mediapackage.Catalog;
import org.opencastproject.mediapackage.MediaPackage;
import org.opencastproject.mediapackage.MediaPackageBuilderFactory;
import org.opencastproject.mediapackage.MediaPackageElement;
import org.opencastproject.mediapackage.MediaPackageElement.Type;
import org.opencastproject.mediapackage.MediaPackageElementBuilderFactory;
import org.opencastproject.mediapackage.MediaPackageElements;
import org.opencastproject.util.IoSupport;
import org.opencastproject.util.MimeTypes;
import org.opencastproject.util.data.Collections;
import org.opencastproject.util.data.Option;
import org.opencastproject.util.persistencefn.PersistenceEnv;
import org.opencastproject.util.persistencefn.PersistenceEnvs;
import org.opencastproject.util.persistencefn.Queries;
import org.opencastproject.workspace.api.Workspace;
import com.entwinemedia.fn.Fn;
import com.entwinemedia.fn.FnX;
import com.entwinemedia.fn.P1;
import com.entwinemedia.fn.P1Lazy;
import com.entwinemedia.fn.Stream;
import com.entwinemedia.fn.data.Opt;
import com.mysema.query.jpa.impl.JPAQuery;
import org.easymock.EasyMock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.net.URI;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.EntityManager;
/**
* Base class for {@link AssetManager} tests.
* <p>
* See {@link org.opencastproject.util.persistencefn.PersistenceUtil#mkTestEntityManagerFactoryFromSystemProperties(String)}
* for command line configuration options.
* <p>
* Implementations of this class need to call {@link #setUp(AssetManager)} to setup the necessary variables prior to
* running a test. You may implement a {@link org.junit.Before} annotated method like this:
* <pre>
* |@Before
* |public void setUp() throws Exception {
* | setUp(mkAbstractAssetManager());
* |}
* </pre>
*/
// CHECKSTYLE:OFF
public abstract class AssetManagerTestBase<A extends AssetManager> {
protected static final Logger logger = LoggerFactory.getLogger(AssetManagerTestBase.class);
public static final String PERSISTENCE_UNIT = "org.opencastproject.assetmanager.impl";
protected static final String OWNER = "test";
/** The asset manager under test. */
protected A am;
protected AQueryBuilder q;
protected Props p;
protected Props p2;
protected PersistenceEnv penv;
/**
* Return the underlying instance of {@link AbstractAssetManager}.
* If the asset manager under test is of type AbstractAssetManager just return that instance.
*/
public abstract AbstractAssetManager getAbstractAssetManager();
public abstract String getCurrentOrgId();
public final void setUp(A assetManager) {
am = assetManager;
q = am.createQuery();
p = new Props(q, "org.opencastproject.service");
p2 = new Props(q, "org.opencastproject.service.sub");
}
public static MediaPackage mkMediaPackage(MediaPackageElement... elements) throws Exception {
final MediaPackage mp = MediaPackageBuilderFactory.newInstance().newMediaPackageBuilder().createNew();
for (MediaPackageElement e : elements) {
mp.add(e);
}
return mp;
}
public static Catalog mkCatalog() throws Exception {
final Catalog mpe = (Catalog) MediaPackageElementBuilderFactory.newInstance().newElementBuilder().newElement(Type.Catalog, MediaPackageElements.EPISODE);
mpe.setURI(new URI("http://dummy.org"));
mpe.setMimeType(MimeTypes.XML);
return mpe;
}
static <A> int sizeOf(Stream<A> stream) {
int count = 0;
for (A ignore : stream) {
count++;
}
return count;
}
static P1<Integer> inc() {
return new P1Lazy<Integer>() {
private int i = 0;
@Override public Integer get1() {
return i++;
}
};
}
@SafeVarargs
public static <A> A[] $a(A... as) {
return as;
}
/**
* Create a number of media packages with one catalog each and add it to the AssetManager. Return the media package IDs as an array.
* <p>
* Please note that each media package creates two assets in the store--the catalog and the manifest--but only one asset
* in the database which is the catalog. The manifest is represented in the snapshot table, not the asset table.
*
* @param amount
* the amount of media packages to create
* @param minVersions
* the minimum amount of versions to create per media package
* @param maxVersions
* the maximum amount of versions to create per media package
* @param seriesId
* an optional series ID
*/
protected String[] createAndAddMediaPackagesSimple(int amount, final int minVersions, final int maxVersions, final Opt<String> seriesId) {
return $(createAndAddMediaPackages(amount, minVersions, maxVersions, seriesId)).map(Snapshots.getMediaPackageId).toSet().toArray(new String[]{});
}
/**
* Like {@link #createAndAddMediaPackagesSimple(int, int, int, Opt)} but without series ID.
*/
protected String[] createAndAddMediaPackagesSimple(int amount, final int minVersions, final int maxVersions) {
return createAndAddMediaPackagesSimple(amount, minVersions, maxVersions, Opt.<String>none());
}
/**
* Continuous versions.
*
* @see #createAndAddMediaPackages(int, int, int, boolean, Opt)
*/
protected Snapshot[] createAndAddMediaPackages(
int amount, final int minVersions, final int maxVersions, final Opt<String> seriesId) {
return createAndAddMediaPackages(amount, minVersions, maxVersions, true, seriesId);
}
/**
* @param continuousVersions true if version numbers should be increased continuously, false if there should be
* discontinuities
* @see #createAndAddMediaPackagesSimple(int, int, int, Opt)
*/
protected Snapshot[] createAndAddMediaPackages(
int amount,
final int minVersions, final int maxVersions,
final boolean continuousVersions,
final Opt<String> seriesId) {
logger.info(format("Create %s media packages with %d to %d snapshots each", amount, minVersions, maxVersions));
final Stream<Snapshot> inserts = Stream.cont(inc()).take(amount).bind(new FnX<Integer, Iterable<Snapshot>>() {
@Override public Iterable<Snapshot> applyX(final Integer mpCount) throws Exception {
final MediaPackage mp = mkMediaPackage(mkCatalog());
for (String sid : seriesId) {
mp.setSeries(sid);
}
final int versions = (int) (Math.random() * ((double) maxVersions - minVersions) + minVersions);
final String mpId = mp.getIdentifier().toString();
logger.debug(format("Going to take %d snapshot/s of media package %s", versions, mpId));
return Stream.cont(inc()).take(versions).map(new Fn<Integer, Snapshot>() {
@Override public Snapshot apply(Integer versionCount) {
if (!continuousVersions) {
// insert a gap into the version claim
getAbstractAssetManager().getDb().claimVersion(mp.getIdentifier().toString());
}
logger.debug(format("Taking snapshot %d of media package %s", versionCount + 1, mpId));
return am.takeSnapshot(OWNER, mp);
}
});
}
});
return Collections.toArray(Snapshot.class, inserts.toList());
}
/* ------------------------------------------------------------------------------------------------------------------ */
/**
* A property schema definition.
*/
public static class Props extends PropertySchema {
public Props(AQueryBuilder q, String namespace) {
super(q, namespace);
}
// define your properties here below
// CHECKSTYLE:OFF
public final PropertyField<Long> count = longProp("count");
public final PropertyField<Boolean> approved = booleanProp("approved");
public final PropertyField<Date> start = dateProp("start");
public final PropertyField<Date> end = dateProp("end");
public final PropertyField<String> legacyId = stringProp("legacyId");
public final PropertyField<String> agent = stringProp("agent");
public final PropertyField<String> seriesId = stringProp("series");
public final PropertyField<Version> versionId = versionProp("version");
// CHECKSTYLE:ON
}
/**
* Create a new test asset manager.
*/
protected AbstractAssetManager mkAbstractAssetManager() throws Exception {
penv = PersistenceEnvs.mkTestEnvFromSystemProperties(PERSISTENCE_UNIT);
// empty database
penv.tx(new Fn<EntityManager, Object>() {
@Override public Object apply(EntityManager entityManager) {
Queries.sql.update(entityManager, "delete from mh_assets_asset");
Queries.sql.update(entityManager, "delete from mh_assets_properties");
Queries.sql.update(entityManager, "delete from mh_assets_snapshot");
Queries.sql.update(entityManager, "delete from mh_assets_version_claim");
return null;
}
});
final Database db = new Database(penv);
//
final Workspace workspace = EasyMock.createNiceMock(Workspace.class);
EasyMock.expect(workspace.get(EasyMock.<URI>anyObject())).andReturn(IoSupport.classPathResourceAsFile("/dublincore-a.xml").get()).anyTimes();
EasyMock.replay(workspace);
//
final AssetStore assetStore = mkAssetStore();
//
return new AbstractAssetManager() {
@Override public Database getDb() {
return db;
}
@Override public AssetStore getAssetStore() {
return assetStore;
}
@Override public HttpAssetProvider getHttpAssetProvider() {
// identity provider
return new HttpAssetProvider() {
@Override public Snapshot prepareForDelivery(Snapshot snapshot) {
return snapshot;
}
};
}
@Override protected Workspace getWorkspace() {
return workspace;
}
@Override protected String getCurrentOrgId() {
return AssetManagerTestBase.this.getCurrentOrgId();
}
};
}
/**
* Create a test asset store.
*/
protected AssetStore mkAssetStore() {
return new AssetStore() {
private Set<StoragePath> store = new HashSet<>();
private void logSize() {
logger.debug(format("Store contains %d asset/s", store.size()));
}
@Override public void put(StoragePath path, Source source) throws AssetStoreException {
store.add(path);
logSize();
}
@Override public boolean copy(StoragePath from, StoragePath to) throws AssetStoreException {
if (store.contains(from)) {
store.add(to);
logSize();
return true;
} else {
return false;
}
}
@Override public Opt<InputStream> get(StoragePath path) throws AssetStoreException {
return IoSupport.openClassPathResource("/dublincore-a.xml").toOpt();
}
@Override public boolean contains(StoragePath path) throws AssetStoreException {
return store.contains(path);
}
@Override public boolean delete(DeletionSelector sel) throws AssetStoreException {
logger.info("Delete from asset store " + sel);
final Set<StoragePath> newStore = new HashSet<>();
boolean deleted = false;
for (StoragePath s : store) {
if (!(sel.getOrganizationId().equals(s.getOrganizationId())
&& sel.getMediaPackageId().equals(s.getMediaPackageId())
&& sel.getVersion().map(eq(s.getVersion())).getOr(true))) {
newStore.add(s);
} else {
deleted = true;
}
}
store = newStore;
logSize();
return deleted;
}
@Override public Option<Long> getTotalSpace() {
return Option.none();
}
@Override public Option<Long> getUsableSpace() {
return Option.none();
}
@Override public Option<Long> getUsedSpace() {
return Option.some((long) store.size());
}
};
}
long runCount(final JPAQuery q) {
return penv.tx(new Fn<EntityManager, Long>() {
@Override public Long apply(EntityManager em) {
return q.clone(em, Database.TEMPLATES).count();
}
});
}
void assertStoreSize(long size) {
assertEquals("Assets in store", size, (long) getAbstractAssetManager().getAssetStore().getUsedSpace().get());
}
}
// CHECKSTYLE:ON