/*
* Copyright (C) 2005-2011 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.opencmis;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import junit.framework.TestCase;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.filestore.FileContentWriter;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.GUID;
import org.alfresco.util.TempFileProvider;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.chemistry.opencmis.client.api.Document;
import org.apache.chemistry.opencmis.client.api.Folder;
import org.apache.chemistry.opencmis.client.api.Repository;
import org.apache.chemistry.opencmis.client.api.Session;
import org.apache.chemistry.opencmis.client.api.SessionFactory;
import org.apache.chemistry.opencmis.client.runtime.SessionFactoryImpl;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.SessionParameter;
import org.apache.chemistry.opencmis.commons.data.CmisExtensionElement;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.enums.BindingType;
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
import org.apache.chemistry.opencmis.commons.exceptions.CmisStorageException;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
import org.apache.chemistry.opencmis.commons.impl.server.AbstractServiceFactory;
import org.apache.chemistry.opencmis.commons.server.CallContext;
import org.apache.chemistry.opencmis.commons.server.CmisService;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
/**
* Tests basic local CMIS interaction
*
* @author Derek Hulley
* @since 4.0
*/
public class OpenCmisLocalTest extends TestCase
{
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
/**
* Test class to provide the service factory
*
* @author Derek Hulley
* @since 4.0
*/
public static class TestCmisServiceFactory extends AbstractServiceFactory
{
private static AlfrescoCmisServiceFactory serviceFactory = (AlfrescoCmisServiceFactory) ctx.getBean("CMISServiceFactory");
@Override
public void init(Map parameters)
{
serviceFactory.init(parameters);
}
@Override
public void destroy()
{
}
@Override
public CmisService getService(CallContext context)
{
return serviceFactory.getService(context);
}
}
private Repository getRepository(String user, String password)
{
// default factory implementation
SessionFactory sessionFactory = SessionFactoryImpl.newInstance();
Map parameters = new HashMap();
// user credentials
parameters.put(SessionParameter.USER, "admin");
parameters.put(SessionParameter.PASSWORD, "admin");
// connection settings
parameters.put(SessionParameter.BINDING_TYPE, BindingType.LOCAL.value());
parameters.put(SessionParameter.LOCAL_FACTORY, "org.alfresco.opencmis.OpenCmisLocalTest$TestCmisServiceFactory");
// create session
List repositories = sessionFactory.getRepositories(parameters);
return repositories.size() > 0 ? repositories.get(0) : null;
}
public void setUp() throws Exception
{
}
public void testVoid()
{
}
public void DISABLED_testSetUp() throws Exception
{
Repository repository = getRepository("admin", "admin");
assertNotNull("No repository available for testing", repository);
}
public void DISABLED_testBasicFileOps()
{
Repository repository = getRepository("admin", "admin");
Session session = repository.createSession();
Folder rootFolder = session.getRootFolder();
// create folder
Map folderProps = new HashMap();
{
folderProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:folder");
folderProps.put(PropertyIds.NAME, getName() + "-" + GUID.generate());
}
Folder folder = rootFolder.createFolder(folderProps, null, null, null, session.getDefaultContext());
Map fileProps = new HashMap();
{
fileProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:document");
fileProps.put(PropertyIds.NAME, "mydoc-" + GUID.generate() + ".txt");
}
ContentStreamImpl fileContent = new ContentStreamImpl();
{
ContentWriter writer = new FileContentWriter(TempFileProvider.createTempFile(getName(), ".txt"));
writer.putContent("Ipsum and so on");
ContentReader reader = writer.getReader();
fileContent.setMimeType(MimetypeMap.MIMETYPE_TEXT_PLAIN);
fileContent.setStream(reader.getContentInputStream());
}
folder.createDocument(fileProps, fileContent, VersioningState.MAJOR);
}
public void testALF10085() throws InterruptedException
{
Repository repository = getRepository("admin", "admin");
Session session = repository.createSession();
Folder rootFolder = session.getRootFolder();
Map props = new HashMap();
{
props.put(PropertyIds.OBJECT_TYPE_ID, "D:cmiscustom:document");
props.put(PropertyIds.NAME, "mydoc-" + GUID.generate() + ".txt");
}
Document doc1 = rootFolder.createDocument(props, null, null);
props = new HashMap();
{
props.put(PropertyIds.OBJECT_TYPE_ID, "D:cmiscustom:document");
props.put(PropertyIds.NAME, "mydoc-" + GUID.generate() + ".txt");
}
Document doc2 = rootFolder.createDocument(props, null, null);
Thread.sleep(6000);
session.getObject(doc1);
doc1.refresh();
Calendar doc1LastModifiedBefore = (Calendar)doc1.getProperty(PropertyIds.LAST_MODIFICATION_DATE).getFirstValue();
assertNotNull(doc1LastModifiedBefore);
doc2.refresh();
Calendar doc2LastModifiedBefore = (Calendar)doc2.getProperty(PropertyIds.LAST_MODIFICATION_DATE).getFirstValue();
assertNotNull(doc2LastModifiedBefore);
// Add relationship A to B
props = new HashMap();
{
props.put(PropertyIds.OBJECT_TYPE_ID, "R:cmiscustom:assoc");
props.put(PropertyIds.NAME, "A Relationship");
props.put(PropertyIds.SOURCE_ID, doc1.getId());
props.put(PropertyIds.TARGET_ID, doc2.getId());
}
session.createRelationship(props);
doc1.refresh();
Calendar doc1LastModifiedAfter = (Calendar)doc1.getProperty(PropertyIds.LAST_MODIFICATION_DATE).getFirstValue();
assertNotNull(doc1LastModifiedAfter);
doc2.refresh();
Calendar doc2LastModifiedAfter = (Calendar)doc2.getProperty(PropertyIds.LAST_MODIFICATION_DATE).getFirstValue();
assertNotNull(doc2LastModifiedAfter);
assertEquals(doc1LastModifiedBefore, doc1LastModifiedAfter);
assertEquals(doc2LastModifiedBefore, doc2LastModifiedAfter);
}
// Test we don't get an exception with the interceptor
public void testAlfrescoCmisStreamInterceptor() throws Exception
{
simulateCallWithAdvice(true);
}
// Test we do get an exception without the interceptor
public void testAlfrescoNonCmisStreamInterceptor() throws Exception
{
try
{
simulateCallWithAdvice(false);
fail("Expected an Exception reading InputStream a second time");
}
catch (CmisStorageException e)
{
// ignore expected
}
}
/**
* Simulates the pattern of advice created by AlfrescoCmisServiceFactory.getService(CallContext),
* optionally including a AlfrescoCmisStreamInterceptor which changes ContentStream parameter
* values into ReusableContentStream parameters which unlike the original may be closed and then
* opened again. This is important as retrying transaction advice is also added (simulated here
* by the afterAdvice and the test target object. See MNT-285
* @param includeStreamInterceptor
*/
private void simulateCallWithAdvice(boolean includeStreamInterceptor) throws Exception
{
final int loops = 3;
final AtomicInteger beforeAdviceCount = new AtomicInteger(0);
final AtomicInteger afterAdviceCount = new AtomicInteger(0);
final AtomicInteger targetCount = new AtomicInteger(0);
MethodInterceptor beforeAdvice = new MethodInterceptor()
{
@Override
public Object invoke(MethodInvocation mi) throws Throwable
{
beforeAdviceCount.incrementAndGet();
return mi.proceed();
}
};
AlfrescoCmisStreamInterceptor interceptor = new AlfrescoCmisStreamInterceptor();
// Represents the retrying transaction
MethodInterceptor afterAdvice = new MethodInterceptor()
{
@Override
public Object invoke(MethodInvocation mi) throws Throwable
{
boolean exit = true;
do
{
try
{
afterAdviceCount.incrementAndGet();
return mi.proceed();
}
catch (RuntimeException e)
{
if ("Test".equals(e.getMessage()))
{
exit = false;
}
else
{
throw e;
}
}
}
while (!exit);
return null;
}
};
TestStreamTarget target = new TestStreamTarget()
{
@Override
public void methodA(ContentStream csa, String str, ContentStream csb, ContentStream csc, int i) throws Exception
{
int count = targetCount.incrementAndGet();
// Use input streams - normally only works once
File a = null;
File b = null;
File c = null;
try
{
a = TempFileProvider.createTempFile(csa.getStream(), "csA", ".txt");
b = TempFileProvider.createTempFile(csb.getStream(), "csB", ".txt");
c = TempFileProvider.createTempFile(null, "csC", ".txt");
// Similar test to that in AlfrescoCmisServiceImpl.copyToTempFile(ContentStream)
if ((csa.getLength() > -1) && (a == null || csa.getLength() != a.length()))
{
throw new CmisStorageException("Expected " + csa.getLength() + " bytes but retrieved " +
(a == null ? -1 : a.length()) + " bytes!");
}
}
finally
{
if (a != null)
{
a.delete();
}
if (b != null)
{
b.delete();
}
if (c != null)
{
c.delete();
}
}
// Force the input stream to be reused
if (count < loops)
{
throw new RuntimeException("Test");
}
}
};
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addInterface(TestStreamTarget.class);
proxyFactory.addAdvice(beforeAdvice);
if (includeStreamInterceptor)
{
proxyFactory.addAdvice(interceptor);
}
proxyFactory.addAdvice(afterAdvice);
TestStreamTarget proxy = (TestStreamTarget) proxyFactory.getProxy();
ContentStreamImpl csa = new ContentStreamImpl("file1", MimetypeMap.MIMETYPE_TEXT_PLAIN, "The cat sat on the mat");
ContentStreamImpl csb = new ContentStreamImpl("file2", MimetypeMap.MIMETYPE_TEXT_PLAIN, "and the cow jumped over the moon.");
proxy.methodA(csa, "ignored", csb, null, 10);
assertEquals("beforeAdvice count", 1, beforeAdviceCount.intValue());
assertEquals("afterAdvice count", 1, beforeAdviceCount.intValue());
assertEquals("target count", loops, targetCount.intValue());
}
private interface TestStreamTarget
{
void methodA(ContentStream csa, String str, ContentStream csb, ContentStream csc, int i) throws Exception;
}
}