/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see .
*/
package org.alfresco.repo.domain.contentdata;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import junit.framework.TestCase;
import org.alfresco.repo.content.ContentContext;
import org.alfresco.repo.content.ContentStore;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.filestore.FileContentStore;
import org.alfresco.repo.domain.contentdata.ContentDataDAO.ContentUrlHandler;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.Pair;
import org.alfresco.util.TempFileProvider;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.dao.DataIntegrityViolationException;
/**
* @see ContentDataDAO
*
* @author Derek Hulley
* @since 3.2
*/
public class ContentDataDAOTest extends TestCase
{
private ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) ApplicationContextHelper.getApplicationContext();
private TransactionService transactionService;
private RetryingTransactionHelper txnHelper;
private ContentDataDAO contentDataDAO;
private ContentStore contentStore;
@Override
public void setUp() throws Exception
{
ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
transactionService = serviceRegistry.getTransactionService();
txnHelper = transactionService.getRetryingTransactionHelper();
contentDataDAO = (ContentDataDAO) ctx.getBean("contentDataDAO");
contentStore = new FileContentStore(ctx, TempFileProvider.getTempDir());
}
private Pair create(final ContentData contentData)
{
RetryingTransactionCallback> callback = new RetryingTransactionCallback>()
{
public Pair execute() throws Throwable
{
Pair contentDataPair = contentDataDAO.createContentData(contentData);
return contentDataPair;
}
};
return txnHelper.doInTransaction(callback, false, false);
}
private Pair update(final Long id, final ContentData contentData)
{
RetryingTransactionCallback> callback = new RetryingTransactionCallback>()
{
public Pair execute() throws Throwable
{
contentDataDAO.updateContentData(id, contentData);
return new Pair(id, contentData);
}
};
return txnHelper.doInTransaction(callback, false, false);
}
private void delete(final Long id)
{
RetryingTransactionCallback callback = new RetryingTransactionCallback()
{
public Void execute() throws Throwable
{
contentDataDAO.deleteContentData(id);
return null;
}
};
txnHelper.doInTransaction(callback, false, false);
}
/**
* Retrieves and checks the ContentData for equality
*/
private void getAndCheck(final Long contentDataId, ContentData checkContentData)
{
RetryingTransactionCallback> callback = new RetryingTransactionCallback>()
{
public Pair execute() throws Throwable
{
Pair contentDataPair = contentDataDAO.getContentData(contentDataId);
return contentDataPair;
}
};
Pair resultPair = txnHelper.doInTransaction(callback, true, false);
assertNotNull("Failed to find result for ID " + contentDataId, resultPair);
assertEquals("ContentData retrieved not the same as persisted: ", checkContentData, resultPair.getSecond());
}
private ContentData getContentData()
{
ContentContext contentCtx = new ContentContext(null, null);
String contentUrl = contentStore.getWriter(contentCtx).getContentUrl();
ContentData contentData = new ContentData(
contentUrl,
MimetypeMap.MIMETYPE_TEXT_PLAIN,
12335L,
"UTF-8",
Locale.FRENCH);
return contentData;
}
public void testGetWithInvalidId()
{
try
{
contentDataDAO.getContentData(-1L);
fail("Invalid ContentData IDs must generate DataIntegrityViolationException.");
}
catch (DataIntegrityViolationException e)
{
// Expected
}
}
/**
* Check that the ContentData
is decoded and persisted correctly.
*/
public void testCreateContentDataSimple() throws Exception
{
ContentData contentData = getContentData();
Pair resultPair = create(contentData);
getAndCheck(resultPair.getFirst(), contentData);
}
/**
* Check that the ContentData
is decoded and persisted correctly.
*/
public void testCreateContentDataNulls() throws Exception
{
ContentData contentData = new ContentData(null, null, 0L, null, null);
Pair resultPair = create(contentData);
getAndCheck(resultPair.getFirst(), contentData);
}
/**
* Ensure that upper and lowercase URLs don't clash
* @throws Exception
*/
public void testEnsureCaseSensitiveStorage() throws Exception
{
ContentData contentData = getContentData();
String contentUrlUpper = contentData.getContentUrl().toUpperCase();
ContentData contentDataUpper = new ContentData(
contentUrlUpper, MimetypeMap.MIMETYPE_TEXT_PLAIN, 0L, "UTF-8", new Locale("FR"));
String contentUrlLower = contentData.getContentUrl().toLowerCase();
ContentData contentDataLower = new ContentData(
contentUrlLower, MimetypeMap.MIMETYPE_TEXT_PLAIN, 0L, "utf-8", new Locale("fr"));
Pair resultPairUpper = create(contentDataUpper);
getAndCheck(resultPairUpper.getFirst(), contentDataUpper);
Pair resultPairLower = create(contentDataLower);
getAndCheck(resultPairLower.getFirst(), contentDataLower);
}
public void testUpdate() throws Exception
{
ContentData contentData = getContentData();
Pair resultPair = create(contentData);
Long id = resultPair.getFirst();
// Update
contentData = ContentData.setMimetype(contentData, MimetypeMap.MIMETYPE_HTML);
contentData = ContentData.setEncoding(contentData, "UTF-16");
// Don't update the content itself
update(id, contentData);
// Check
getAndCheck(id, contentData);
}
public void testDelete() throws Exception
{
ContentData contentData = getContentData();
Pair resultPair = create(contentData);
getAndCheck(resultPair.getFirst(), contentData);
delete(resultPair.getFirst());
try
{
getAndCheck(resultPair.getFirst(), contentData);
fail("Entity still exists");
}
catch (Throwable e)
{
// Expected
}
}
/**
* Check that orphaned content can be re-instated.
*/
public void testReinstate_ALF3867()
{
ContentData contentData = getContentData();
Pair resultPair = create(contentData);
getAndCheck(resultPair.getFirst(), contentData);
delete(resultPair.getFirst());
// Now create a ContentData with the same URL
create(contentData);
}
public void testContentUrl_FetchingOrphansNoLimit() throws Exception
{
ContentData contentData = getContentData();
Pair resultPair = create(contentData);
getAndCheck(resultPair.getFirst(), contentData);
delete(resultPair.getFirst());
// The content URL is orphaned
final String contentUrlOrphaned = contentData.getContentUrl();
final boolean[] found = new boolean[] {false};
// Iterate over all orphaned content URLs and ensure that we hit the one we just orphaned
ContentUrlHandler handler = new ContentUrlHandler()
{
public void handle(Long id, String contentUrl, Long orphanTime)
{
// Check
if (id == null || contentUrl == null || orphanTime == null)
{
fail("Invalid orphan data returned to handler: " + id + "-" + contentUrl + "-" + orphanTime);
}
// Did we get the one we wanted?
if (contentUrl.equals(contentUrlOrphaned))
{
found[0] = true;
}
}
};
contentDataDAO.getContentUrlsOrphaned(handler, Long.MAX_VALUE);
assertTrue("Newly-orphaned content URL not found", found[0]);
}
public void testContentUrl_FetchingOrphansWithLimit() throws Exception
{
// Orphan some content
for (int i = 0; i < 5; i++)
{
ContentData contentData = getContentData();
Pair resultPair = create(contentData);
getAndCheck(resultPair.getFirst(), contentData);
delete(resultPair.getFirst());
}
final int[] count = new int[] {0};
// Iterate over all orphaned content URLs and ensure that we hit the one we just orphaned
ContentUrlHandler handler = new ContentUrlHandler()
{
public void handle(Long id, String contentUrl, Long orphanTime)
{
// Check
if (id == null || contentUrl == null || orphanTime == null)
{
fail("Invalid orphan data returned to handler: " + id + "-" + contentUrl + "-" + orphanTime);
}
count[0]++;
}
};
contentDataDAO.getContentUrlsOrphaned(handler, Long.MAX_VALUE, 5);
assertEquals("Expected exactly 5 results callbacks", 5, count[0]);
}
private static final String[] MIMETYPES = new String[]
{
MimetypeMap.MIMETYPE_ACP,
MimetypeMap.MIMETYPE_EXCEL,
MimetypeMap.MIMETYPE_IMAGE_JPEG,
MimetypeMap.MIMETYPE_JAVASCRIPT,
MimetypeMap.MIMETYPE_RSS
};
private static final String[] ENCODINGS = new String[]
{
"utf-8",
"ascii",
"latin1",
"wibbles",
"iso-whatever"
};
private static final Locale[] LOCALES = new Locale[]
{
Locale.FRENCH,
Locale.CHINESE,
Locale.ITALIAN,
Locale.JAPANESE,
Locale.ENGLISH
};
private List> speedTestWrite(String name, int total)
{
System.out.println("Starting write speed test: " + name);
long start = System.nanoTime();
List> pairs = new ArrayList>(100000);
// Loop and check for performance degradation
for (int i = 0; i < (total / 200 / 5); i++)
{
for (int j = 0; j < 200; j++)
{
for (int k = 0; k < 5; k++)
{
ContentData contentData = getContentData();
String contentUrl = contentData.getContentUrl();
contentData = new ContentData(
contentUrl,
MIMETYPES[k],
(long) j*k,
ENCODINGS[k],
LOCALES[k]);
Pair pair = create(contentData);
pairs.add(pair);
}
}
// That's 1000
long now = System.nanoTime();
double diffMs = (double) (now - start) / 1E6;
double aveMs = diffMs / (double) pairs.size();
String msg = String.format(
" Wrote %7d rows; average is %5.2f ms per row or %5.2f rows per second",
pairs.size(),
aveMs,
1000.0 / aveMs);
System.out.println(msg);
}
// Done
return pairs;
}
private void speedTestRead(String name, List> pairs)
{
System.out.println("Starting read speed test: " + name);
long start = System.nanoTime();
// Loop and check for performance degradation
int num = 1;
for (Pair pair : pairs)
{
Long id = pair.getFirst();
ContentData contentData = pair.getSecond();
// Retrieve it
getAndCheck(id, contentData);
// Report
if (num % 1000 == 0)
{
long now = System.nanoTime();
double diffMs = (double) (now - start) / 1E6;
double aveMs = diffMs / (double) num;
String msg = String.format(
" Read %7d rows; average is %5.2f ms per row or %5.2f rows per second",
num,
aveMs,
1000.0 / aveMs);
System.out.println(msg);
}
num++;
}
// Done
}
public void testCreateSpeedIndividualTxns()
{
List> pairs = speedTestWrite(getName(), 2000);
speedTestRead(getName(), pairs);
}
public void testCreateSpeedSingleTxn()
{
RetryingTransactionCallback>> writeCallback = new RetryingTransactionCallback>>()
{
public List> execute() throws Throwable
{
return speedTestWrite(getName(), 10000);
}
};
final List> pairs = txnHelper.doInTransaction(writeCallback, false, false);
RetryingTransactionCallback readCallback = new RetryingTransactionCallback()
{
public Void execute() throws Throwable
{
speedTestRead(getName(), pairs);
return null;
}
};
txnHelper.doInTransaction(readCallback, false, false);
}
}