/*
* Copyright 2013-2016 EMC Corporation. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0.txt
*
* or in the "license" file accompanying this file. This file 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 com.emc.ecs.sync.storage;
import com.emc.ecs.sync.EcsSync;
import com.emc.ecs.sync.config.SyncConfig;
import com.emc.ecs.sync.config.SyncOptions;
import com.emc.ecs.sync.config.storage.CasConfig;
import com.emc.ecs.sync.rest.LogLevel;
import com.emc.ecs.sync.service.SyncJobService;
import com.emc.ecs.sync.test.ByteAlteringFilter;
import com.emc.ecs.sync.test.TestConfig;
import com.emc.ecs.sync.util.Iso8601Util;
import com.filepool.fplibrary.*;
import org.apache.commons.codec.binary.Hex;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CasStorageTest {
private static final Logger log = LoggerFactory.getLogger(CasStorageTest.class);
private static final int CLIP_OPTIONS = 0;
private static final int BUFFER_SIZE = 1048576; // 1MB
private static final int CAS_THREADS = 32;
private static final int CAS_SETUP_WAIT_MINUTES = 5;
private String connectString1, connectString2;
private LogLevel logLevel;
@Before
public void setup() throws Exception {
logLevel = SyncJobService.getInstance().getLogLevel();
SyncJobService.getInstance().setLogLevel(LogLevel.verbose);
try {
Properties syncProperties = TestConfig.getProperties();
connectString1 = syncProperties.getProperty(TestConfig.PROP_CAS_CONNECT_STRING);
connectString2 = syncProperties.getProperty(TestConfig.PROP_CAS_CONNECT_STRING + "2");
Assume.assumeNotNull(connectString1, connectString2);
} catch (FileNotFoundException e) {
Assume.assumeFalse("Could not load ecs-sync.properties", true);
}
}
public void tearDown() throws Exception {
SyncJobService.getInstance().setLogLevel(logLevel);
}
@Test
public void testPipedStreams() throws Exception {
Random random = new Random();
// test smaller than pipe buffer
byte[] source = new byte[random.nextInt(BUFFER_SIZE) + 1];
random.nextBytes(source);
String md5 = Hex.encodeHexString(MessageDigest.getInstance("MD5").digest(source));
Assert.assertEquals("MD5 mismatch", md5, pipeAndGetMd5(source));
// test larger than pipe buffer
source = new byte[random.nextInt(BUFFER_SIZE) + BUFFER_SIZE + 1];
random.nextBytes(source);
md5 = Hex.encodeHexString(MessageDigest.getInstance("MD5").digest(source));
Assert.assertEquals("MD5 mismatch", md5, pipeAndGetMd5(source));
}
private String pipeAndGetMd5(byte[] source) throws Exception {
PipedInputStream pin = new PipedInputStream(BUFFER_SIZE);
PipedOutputStream pout = new PipedOutputStream(pin);
Producer producer = new Producer(source, pout);
// produce in parallel
Thread producerThread = new Thread(producer);
producerThread.start();
// consume inside this thread
byte[] dest = new byte[source.length];
try {
int read = 0;
while (read < dest.length && read != -1) {
read += pin.read(dest, read, dest.length - read);
}
} finally {
try {
pin.close();
} catch (Throwable t) {
// ignore
}
}
// synchronize
producerThread.join();
return Hex.encodeHexString(MessageDigest.getInstance("MD5").digest(dest));
}
@Test
public void testCasSingleObject() throws Exception {
FPPool sourcePool = new FPPool(connectString1);
FPPool targetPool = new FPPool(connectString2);
try {
// create clip in source (<=1MB blob size) - capture summary for comparison
StringWriter sourceSummary = new StringWriter();
List<String> clipIds = createTestClips(sourcePool, 1048576, 1, sourceSummary);
String clipID = clipIds.iterator().next();
// open clip in source
FPClip clip = new FPClip(sourcePool, clipID, FPLibraryConstants.FP_OPEN_FLAT);
// buffer CDF
ByteArrayOutputStream baos = new ByteArrayOutputStream();
clip.RawRead(baos);
// write CDF to target
FPClip targetClip = new FPClip(targetPool, clipID, new ByteArrayInputStream(baos.toByteArray()), CLIP_OPTIONS);
// migrate blobs
FPTag tag, targetTag;
int tagCount = 0;
while ((tag = clip.FetchNext()) != null) {
targetTag = targetClip.FetchNext();
Assert.assertEquals("Tag names don't match", tag.getTagName(), targetTag.getTagName());
Assert.assertTrue("Tag " + tag.getTagName() + " attributes not equal",
Arrays.equals(tag.getAttributes(), targetTag.getAttributes()));
int blobStatus = tag.BlobExists();
if (blobStatus == 1) {
PipedInputStream pin = new PipedInputStream(BUFFER_SIZE);
PipedOutputStream pout = new PipedOutputStream(pin);
BlobReader reader = new BlobReader(tag, pout);
// start reading in parallel
Thread readThread = new Thread(reader);
readThread.start();
// write inside this thread
targetTag.BlobWrite(pin);
readThread.join(); // this shouldn't do anything, but just in case
if (!reader.isSuccess()) throw new Exception("blob read failed", reader.getError());
} else {
if (blobStatus != -1)
System.out.println("blob unavailable, clipId=" + clipID + ", tagNum=" + tagCount + ", blobStatus=" + blobStatus);
}
tag.Close();
targetTag.Close();
tagCount++;
}
clip.Close();
Assert.assertEquals("clip IDs not equal", clipID, targetClip.Write());
targetClip.Close();
// check target blob data
targetClip = new FPClip(targetPool, clipID, FPLibraryConstants.FP_OPEN_FLAT);
Assert.assertEquals("content mismatch", sourceSummary.toString(), summarizeClip(targetClip));
targetClip.Close();
// delete in source and target
FPClip.Delete(sourcePool, clipID);
FPClip.Delete(targetPool, clipID);
} finally {
try {
sourcePool.Close();
} catch (Throwable t) {
log.warn("failed to close source pool", t);
}
try {
targetPool.Close();
} catch (Throwable t) {
log.warn("failed to close dest pool", t);
}
}
}
@Test
public void testReopenClip() throws Exception {
FPPool pool = new FPPool(connectString1);
String clipID = null;
try {
// create clip in source (<=1MB blob size) - capture summary for comparison
StringWriter sourceSummary = new StringWriter();
List<String> clipIds = createTestClips(pool, 1048576, 1, sourceSummary);
clipID = clipIds.iterator().next();
// open clip
FPClip clip = new FPClip(pool, clipID);
long size = clip.getTotalSize();
log.info("clip {} has total size {} bytes", clipID, size);
// close clip
clip.Close();
// reopen clip
clip = new FPClip(pool, clipID, FPLibraryConstants.FP_OPEN_FLAT);
Assert.assertNotNull(clip);
clip.Close();
} finally {
if (clipID != null) FPClip.Delete(pool, clipID);
try {
pool.Close();
} catch (Throwable t) {
log.warn("failed to close pool", t);
}
}
}
@Test
public void testSyncSingleClip() throws Exception {
testSyncClipList(1, 102400);
}
@Test
public void testSyncClipListSmallBlobs() throws Exception {
int numClips = 250, maxBlobSize = 102400;
testSyncClipList(numClips, maxBlobSize);
}
@Test
public void testSyncClipListLargeBlobs() throws Exception {
int numClips = 25, maxBlobSize = 2048000;
testSyncClipList(numClips, maxBlobSize);
}
private void testSyncClipList(int numClips, int maxBlobSize) throws Exception {
FPPool sourcePool = new FPPool(connectString1);
FPPool destPool = new FPPool(connectString2);
// create random data (capture summary for comparison)
StringWriter sourceSummary = new StringWriter();
List<String> clipIds = createTestClips(sourcePool, maxBlobSize, numClips, sourceSummary);
try {
// write clip file
File clipFile = File.createTempFile("clip", "lst");
clipFile.deleteOnExit();
BufferedWriter writer = new BufferedWriter(new FileWriter(clipFile));
for (String clipId : clipIds) {
log.debug("created {}", clipId);
writer.write(clipId);
writer.newLine();
}
writer.close();
EcsSync sync = createEcsSync(connectString1, connectString2, CAS_THREADS, true);
sync.getSyncConfig().getOptions().setSourceListFile(clipFile.getAbsolutePath());
run(sync);
Assert.assertEquals(0, sync.getStats().getObjectsFailed());
Assert.assertEquals(numClips, sync.getStats().getObjectsComplete());
String destSummary = summarize(destPool, clipIds);
Assert.assertEquals("query summaries different", sourceSummary.toString(), destSummary);
} finally {
delete(sourcePool, clipIds);
delete(destPool, clipIds);
try {
sourcePool.Close();
} catch (Throwable t) {
log.warn("failed to close source pool", t);
}
try {
destPool.Close();
} catch (Throwable t) {
log.warn("failed to close dest pool", t);
}
}
}
@Test
public void testVerify() throws Exception {
FPPool sourcePool = new FPPool(connectString1);
FPPool destPool = new FPPool(connectString2);
// create random data (capture summary for comparison)
StringWriter sourceSummary = new StringWriter();
List<String> clipIds = createTestClips(sourcePool, 10240, 250, sourceSummary);
try {
// write clip file
File clipFile = File.createTempFile("clip", "lst");
clipFile.deleteOnExit();
BufferedWriter writer = new BufferedWriter(new FileWriter(clipFile));
for (String clipId : clipIds) {
writer.write(clipId);
writer.newLine();
}
writer.close();
// test sync with verify
EcsSync sync = createEcsSync(connectString1, connectString2, CAS_THREADS, true);
sync.getSyncConfig().getOptions().setSourceListFile(clipFile.getAbsolutePath());
sync.getSyncConfig().getOptions().setVerify(true);
run(sync);
Assert.assertEquals(0, sync.getStats().getObjectsFailed());
// test verify only
sync = createEcsSync(connectString1, connectString2, CAS_THREADS, true);
sync.getSyncConfig().getOptions().setSourceListFile(clipFile.getAbsolutePath());
sync.getSyncConfig().getOptions().setVerifyOnly(true);
run(sync);
Assert.assertEquals(0, sync.getStats().getObjectsFailed());
// delete clips from both
delete(sourcePool, clipIds);
delete(destPool, clipIds);
// create new clips (ECS has a problem reading previously deleted and recreated clip IDs)
clipIds = createTestClips(sourcePool, 10240, 250, sourceSummary);
writer = new BufferedWriter(new FileWriter(clipFile));
for (String clipId : clipIds) {
writer.write(clipId);
writer.newLine();
}
writer.close();
// test sync+verify with failures
sync = createEcsSync(connectString1, connectString2, CAS_THREADS, true);
sync.getSyncConfig().getOptions().setSourceListFile(clipFile.getAbsolutePath());
ByteAlteringFilter.ByteAlteringConfig filter = new ByteAlteringFilter.ByteAlteringConfig();
sync.getSyncConfig().setFilters(Collections.singletonList(filter));
sync.getSyncConfig().getOptions().setRetryAttempts(0); // retries will circumvent this test
sync.getSyncConfig().getOptions().setVerify(true);
run(sync);
Assert.assertTrue(filter.getModifiedObjects() > 0);
Assert.assertEquals(filter.getModifiedObjects(), sync.getStats().getObjectsFailed());
} finally {
// delete clips from both
delete(sourcePool, clipIds);
delete(destPool, clipIds);
try {
sourcePool.Close();
} catch (Throwable t) {
log.warn("failed to close source pool", t);
}
try {
destPool.Close();
} catch (Throwable t) {
log.warn("failed to close dest pool", t);
}
}
}
@Test
public void testSyncQuerySmallBlobs() throws Exception {
int numClips = 250, maxBlobSize = 102400;
FPPool sourcePool = new FPPool(connectString1);
FPPool destPool = new FPPool(connectString2);
// make sure both pools are empty
Assert.assertEquals("source pool contains objects", 0, query(sourcePool).size());
Assert.assertEquals("target pool contains objects", 0, query(destPool).size());
// create random data (capture summary for comparison)
StringWriter sourceSummary = new StringWriter();
List<String> clipIds = createTestClips(sourcePool, maxBlobSize, numClips, sourceSummary);
try {
EcsSync sync = createEcsSync(connectString1, connectString2, CAS_THREADS, true);
run(sync);
Assert.assertEquals(0, sync.getStats().getObjectsFailed());
Assert.assertEquals(numClips, sync.getStats().getObjectsComplete());
String destSummary = summarize(destPool, query(destPool));
Assert.assertEquals("query summaries different", sourceSummary.toString(), destSummary);
} finally {
delete(sourcePool, clipIds);
delete(destPool, clipIds);
try {
sourcePool.Close();
} catch (Throwable t) {
log.warn("failed to close source pool", t);
}
try {
destPool.Close();
} catch (Throwable t) {
log.warn("failed to close dest pool", t);
}
}
}
@Test
public void testQueryTimes() throws Exception {
int numClips = 100, maxBlobSize = 102400;
FPPool sourcePool = new FPPool(connectString1);
FPPool destPool = new FPPool(connectString2);
// make sure both pools are empty
Assert.assertEquals("source pool contains objects", 0, query(sourcePool).size());
Assert.assertEquals("target pool contains objects", 0, query(destPool).size());
// create random data (capture summary for comparison)
StringWriter sourceSummary = new StringWriter();
List<String> clipIds = createTestClips(sourcePool, maxBlobSize, numClips, sourceSummary);
// compensate for up to 5 seconds of clock skew
Thread.sleep(5000);
Calendar startTime = Calendar.getInstance(), endTime = Calendar.getInstance();
startTime.add(Calendar.MINUTE, -10); // set start time to 10 minutes ago
try {
EcsSync sync = createEcsSync(connectString1, connectString2, CAS_THREADS, false);
// set query start/end times
CasConfig sourceConfig = (CasConfig) sync.getSyncConfig().getSource();
sourceConfig.setQueryStartTime(Iso8601Util.format(startTime.getTime()));
sourceConfig.setQueryEndTime(Iso8601Util.format(endTime.getTime()));
sync.run();
Assert.assertEquals(0, sync.getStats().getObjectsFailed());
Assert.assertEquals(numClips, sync.getStats().getObjectsComplete());
String destSummary = summarize(destPool, query(destPool));
Assert.assertEquals("query summaries different", sourceSummary.toString(), destSummary);
} finally {
delete(sourcePool, clipIds);
delete(destPool, clipIds);
try {
sourcePool.Close();
} catch (Throwable t) {
log.warn("failed to close source pool", t);
}
try {
destPool.Close();
} catch (Throwable t) {
log.warn("failed to close dest pool", t);
}
}
}
@Test
public void testDeleteClipList() throws Exception {
int numClips = 100, maxBlobSize = 512000;
FPPool pool = new FPPool(connectString1);
try {
// get clip count before test
int originalClipCount = query(pool).size();
// create random data
StringWriter sourceSummary = new StringWriter();
List<String> clipIds = createTestClips(pool, maxBlobSize, numClips, sourceSummary);
// verify test clips were created
Assert.assertEquals("wrong test clip count", originalClipCount + numClips, query(pool).size());
// write clip ID file
File clipFile = File.createTempFile("clip", "lst");
clipFile.deleteOnExit();
BufferedWriter writer = new BufferedWriter(new FileWriter(clipFile));
for (String clipId : clipIds) {
writer.write(clipId);
writer.newLine();
}
writer.close();
// construct EcsSync instance
SyncConfig syncConfig = new SyncConfig();
syncConfig.setOptions(new SyncOptions().withThreadCount(CAS_THREADS).withSourceListFile(clipFile.getAbsolutePath())
.withDeleteSource(true));
syncConfig.setSource(new CasConfig().withConnectionString(connectString1));
syncConfig.setTarget(new com.emc.ecs.sync.config.storage.TestConfig());
EcsSync sync = new EcsSync();
sync.setSyncConfig(syncConfig);
// run EcsSync
sync.run();
System.out.println(sync.getStats().getStatsString());
// verify test clips were deleted
int afterDeleteCount = query(pool).size();
if (originalClipCount != afterDeleteCount) {
delete(pool, clipIds);
Assert.fail("test clips not fully deleted");
}
} finally {
try {
pool.Close();
} catch (Throwable t) {
log.warn("failed to close pool", t);
}
}
}
private EcsSync createEcsSync(String connectString1, String connectString2, int threadCount, boolean enableTimings)
throws Exception {
SyncConfig syncConfig = new SyncConfig();
syncConfig.setSource(new CasConfig().withConnectionString(connectString1));
syncConfig.setTarget(new CasConfig().withConnectionString(connectString2));
syncConfig.setOptions(new SyncOptions().withThreadCount(threadCount).withRetryAttempts(1).withTimingsEnabled(enableTimings));
EcsSync sync = new EcsSync();
sync.setSyncConfig(syncConfig);
return sync;
}
protected void run(EcsSync sync) {
System.gc();
long startSize = Runtime.getRuntime().totalMemory();
sync.run();
System.gc();
long endSize = Runtime.getRuntime().totalMemory();
System.out.println(String.format("memory before sync: %d, after sync: %d", startSize, endSize));
System.out.println(sync.getStats().getStatsString());
}
private List<String> createTestClips(FPPool pool, int maxBlobSize, int thisMany, Writer summaryWriter) throws Exception {
ExecutorService service = Executors.newFixedThreadPool(CAS_THREADS);
System.out.print("Creating clips");
List<String> clipIds = Collections.synchronizedList(new ArrayList<String>());
List<String> summaries = Collections.synchronizedList(new ArrayList<String>());
for (int clipIdx = 0; clipIdx < thisMany; clipIdx++) {
service.submit(new ClipWriter(pool, clipIds, maxBlobSize, summaries));
}
service.shutdown();
service.awaitTermination(CAS_SETUP_WAIT_MINUTES, TimeUnit.MINUTES);
service.shutdownNow();
Collections.sort(summaries);
for (String summary : summaries) {
summaryWriter.append(summary);
}
System.out.println();
return clipIds;
}
private void deleteAll(FPPool pool) throws Exception {
delete(pool, query(pool));
}
private void delete(FPPool pool, List<String> clipIds) throws Exception {
ExecutorService service = Executors.newFixedThreadPool(CAS_THREADS);
System.out.print("Deleting clips");
for (String clipId : clipIds) {
service.submit(new ClipDeleter(pool, clipId));
}
service.shutdown();
service.awaitTermination(CAS_SETUP_WAIT_MINUTES, TimeUnit.MINUTES);
service.shutdownNow();
System.out.println();
}
private String summarize(FPPool pool, List<String> clipIds) throws Exception {
List<String> summaries = Collections.synchronizedList(new ArrayList<String>());
ExecutorService service = Executors.newFixedThreadPool(CAS_THREADS);
System.out.print("Summarizing clips");
for (String clipId : clipIds) {
service.submit(new ClipReader(pool, clipId, summaries));
}
service.shutdown();
service.awaitTermination(CAS_SETUP_WAIT_MINUTES, TimeUnit.MINUTES);
service.shutdownNow();
System.out.println();
Collections.sort(summaries);
StringBuilder out = new StringBuilder();
for (String summary : summaries) {
out.append(summary);
}
return out.toString();
}
private List<String> query(FPPool pool) throws Exception {
List<String> clipIds = new ArrayList<>();
System.out.println("Querying for clips");
FPQueryExpression query = new FPQueryExpression();
query.setStartTime(0);
query.setEndTime(-1);
query.setType(FPLibraryConstants.FP_QUERY_TYPE_EXISTING);
FPPoolQuery poolQuery = new FPPoolQuery(pool, query);
FPQueryResult queryResult = null;
boolean searching = true;
while (searching) {
queryResult = poolQuery.FetchResult();
switch (queryResult.getResultCode()) {
case FPLibraryConstants.FP_QUERY_RESULT_CODE_OK:
clipIds.add(queryResult.getClipID());
break;
case FPLibraryConstants.FP_QUERY_RESULT_CODE_INCOMPLETE:
case FPLibraryConstants.FP_QUERY_RESULT_CODE_COMPLETE:
case FPLibraryConstants.FP_QUERY_RESULT_CODE_PROGRESS:
case FPLibraryConstants.FP_QUERY_RESULT_CODE_ERROR:
break;
case FPLibraryConstants.FP_QUERY_RESULT_CODE_END:
System.out.println("End of query reached, exiting.");
searching = false;
break;
default:
// Unknown error, stop running query
throw new RuntimeException("received error: " + queryResult.getResultCode());
}
queryResult.Close();
} //while
queryResult.Close();
poolQuery.Close();
return clipIds;
}
private String summarizeClip(FPClip clip) throws Exception {
FPTag tag;
List<String> tagNames = new ArrayList<>();
List<Long> tagSizes = new ArrayList<>();
List<byte[]> tagByteArrays = new ArrayList<>();
while ((tag = clip.FetchNext()) != null) {
byte[] tagBytes = null;
if (tag.BlobExists() == 1) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
tag.BlobRead(baos);
tagBytes = baos.toByteArray();
}
tagNames.add(tag.getTagName());
tagSizes.add(tag.getBlobSize());
tagByteArrays.add(tagBytes);
tag.Close();
}
return summarizeClip(clip.getClipID(), tagNames, tagSizes, tagByteArrays);
}
private String summarizeClip(String clipId, List<String> tagNames, List<Long> tagSizes, List<byte[]> tagByteArrays) throws NoSuchAlgorithmException {
StringBuilder out = new StringBuilder();
out.append(String.format("Clip ID: %s", clipId)).append("\n");
if (tagNames != null) {
for (int i = 0; i < tagNames.size(); i++) {
String md5 = "n/a";
if (tagByteArrays.get(i) != null)
md5 = Hex.encodeHexString(MessageDigest.getInstance("MD5").digest(tagByteArrays.get(i)));
out.append(String.format("<--tag:%s--> size:%d, md5:%s", tagNames.get(i), tagSizes.get(i), md5)).append("\n");
}
}
return out.toString();
}
private class BlobReader implements Runnable {
private FPTag sourceTag;
private OutputStream out;
private boolean success = false;
private Throwable error;
BlobReader(FPTag sourceTag, OutputStream out) {
this.sourceTag = sourceTag;
this.out = out;
}
@Override
public synchronized void run() {
try {
sourceTag.BlobRead(out);
success = true;
} catch (Throwable t) {
success = false;
error = t;
} finally {
// make sure you always close piped streams!
try {
out.close();
} catch (Throwable t) {
// ignore
}
}
}
Throwable getError() {
return error;
}
boolean isSuccess() {
return success;
}
}
private class ClipWriter implements Runnable {
private FPPool pool;
private List<String> clipIds;
private int maxBlobSize;
private List<String> summaries;
private Random random;
ClipWriter(FPPool pool, List<String> clipIds, int maxBlobSize, List<String> summaries) {
this.pool = pool;
this.clipIds = clipIds;
this.maxBlobSize = maxBlobSize;
this.summaries = summaries;
random = new Random();
}
@Override
public void run() {
try {
FPClip clip = new FPClip(pool);
FPTag topTag = clip.getTopTag();
List<String> tagNames = new ArrayList<>();
List<Long> tagSizes = new ArrayList<>();
List<byte[]> tagByteArrays = new ArrayList<>();
// random number of tags per clip (<= 10)
for (int tagIdx = 0; tagIdx <= random.nextInt(10); tagIdx++) {
FPTag tag = new FPTag(topTag, "test_tag_" + tagIdx);
byte[] blobContent = null;
// random whether tag has blob
if (random.nextBoolean()) {
// random blob length (<= maxBlobSize)
blobContent = new byte[random.nextInt(maxBlobSize) + 1];
// random blob content
random.nextBytes(blobContent);
tag.BlobWrite(new ByteArrayInputStream(blobContent));
}
tagNames.add(tag.getTagName());
tagSizes.add(tag.getBlobSize());
tagByteArrays.add(blobContent);
tag.Close();
}
topTag.Close();
String clipId = clip.Write();
clip.Close();
clipIds.add(clipId);
summaries.add(summarizeClip(clipId, tagNames, tagSizes, tagByteArrays));
System.out.print(".");
} catch (Exception e) {
e.printStackTrace();
if (e instanceof RuntimeException) throw (RuntimeException) e;
throw new RuntimeException(e);
}
}
}
private class ClipReader implements Runnable {
private FPPool pool;
private String clipId;
private List<String> summaries;
ClipReader(FPPool pool, String clipId, List<String> summaries) {
this.pool = pool;
this.clipId = clipId;
this.summaries = summaries;
}
@Override
public void run() {
try {
FPClip clip = new FPClip(pool, clipId, FPLibraryConstants.FP_OPEN_FLAT);
summaries.add(summarizeClip(clip));
clip.Close();
System.out.print(".");
} catch (Exception e) {
if (e instanceof RuntimeException) throw (RuntimeException) e;
throw new RuntimeException(e);
}
}
}
private class ClipDeleter implements Runnable {
private FPPool pool;
private String clipId;
ClipDeleter(FPPool pool, String clipId) {
this.pool = pool;
this.clipId = clipId;
}
@Override
public void run() {
try {
System.out.print(".");
FPClip.Delete(pool, clipId);
} catch (Exception e) {
if (e instanceof RuntimeException) throw (RuntimeException) e;
throw new RuntimeException(e);
}
}
}
private class Producer implements Runnable {
private byte[] data;
private OutputStream out;
Producer(byte[] data, OutputStream out) {
this.data = data;
this.out = out;
}
@Override
public void run() {
try {
out.write(data);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
out.close();
} catch (IOException e) {
System.out.println("could not close output stream" + e.getMessage());
}
}
}
}
}