/*!
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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.
*
* Copyright (c) 2002-2015 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.platform.plugin.services.importexport;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.pentaho.metadata.repository.DomainAlreadyExistsException;
import org.pentaho.metadata.repository.DomainIdNullException;
import org.pentaho.metadata.repository.DomainStorageException;
import org.pentaho.platform.api.engine.ISolutionEngine;
import org.pentaho.platform.api.locale.IPentahoLocale;
import org.pentaho.platform.api.repository.datasource.IDatasourceMgmtService;
import org.pentaho.platform.api.repository2.unified.Converter;
import org.pentaho.platform.api.repository2.unified.IRepositoryFileData;
import org.pentaho.platform.api.repository2.unified.IUnifiedRepository;
import org.pentaho.platform.api.repository2.unified.RepositoryFile;
import org.pentaho.platform.api.repository2.unified.RepositoryFileAcl;
import org.pentaho.platform.api.repository2.unified.RepositoryFileSid;
import org.pentaho.platform.api.repository2.unified.RepositoryFileSid.Type;
import org.pentaho.platform.api.repository2.unified.RepositoryRequest;
import org.pentaho.platform.api.repository2.unified.data.simple.SimpleRepositoryFileData;
import org.pentaho.platform.core.mimetype.MimeType;
import org.pentaho.platform.engine.core.system.PentahoSessionHolder;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.engine.core.system.StandaloneSession;
import org.pentaho.platform.engine.core.system.boot.PlatformInitializationException;
import org.pentaho.platform.engine.services.solution.SolutionEngine;
import org.pentaho.test.platform.engine.core.MicroPlatform;
public class ZipExportProcessorTest {
private static final Locale LOCALE_DEFAULT = Locale.ENGLISH;
private final MimeType MIME_PRPT = new MimeType( "text/prptMimeType", "prpt" );
private static File tempDir;
private DefaultExportHandler exportHandler;
private IUnifiedRepository repo;
private MicroPlatform microPlatform;
private StandaloneSession exportSession;
private Converter defaultConverter;
@BeforeClass
public static void beforeClass() throws IOException {
tempDir = File.createTempFile( "test", null );
tempDir.delete();
tempDir.mkdir();
FileUtils.forceDeleteOnExit( tempDir );
}
@AfterClass
public static void afterClass() throws IOException {
FileUtils.forceDelete( tempDir );
}
@Before
public void init() throws IOException, PlatformInitializationException, DomainIdNullException,
DomainAlreadyExistsException, DomainStorageException {
List<Locale> availableLocales = java.util.Collections.singletonList( LOCALE_DEFAULT );
Properties localePropertries = new Properties();
localePropertries.setProperty( "name1", "value1" );
final RepositoryFile file0 = new RepositoryFile.Builder( "" ).path( "/" ).id( "/" ).folder( true ).build();
final RepositoryFile file1 =
new RepositoryFile.Builder( "home" ).path( "/home/" ).id( "/home/" ).folder( true ).build();
final RepositoryFile file2 =
new RepositoryFile.Builder( "test user" ).path( "/home/test user/" ).id( "/home/test user/" ).folder( true )
.build();
final RepositoryFile file3 =
new RepositoryFile.Builder( "two words" ).path( "/home/test user/two words/" )
.id( "/home/test user/two words/" ).folder( true ).build();
final RepositoryFile fileX =
new RepositoryFile.Builder( "eval (+)%.prpt" ).path( "/home/test user/two words/eval (+)%.prpt" ).id(
"/home/test user/two words/eval (+)%.prpt" ).folder( false ).build();
final RepositoryFile[] repoFiles = new RepositoryFile[] { file0, file1, file2, file3, fileX };
final Map<Serializable, RepositoryFile> repoFilesMap = new HashMap<Serializable, RepositoryFile>();
for ( RepositoryFile f : repoFiles ) {
repoFilesMap.put( f.getId(), f );
}
repo = mock( IUnifiedRepository.class );
final Answer<RepositoryFile> answerRepoGetFile = new Answer<RepositoryFile>() {
@Override
public RepositoryFile answer( InvocationOnMock invocation ) throws Throwable {
Object[] args = invocation.getArguments();
final Object fileId = args[0];
return getRepoFile( repoFilesMap, fileId );
}
};
Mockito.doAnswer( answerRepoGetFile ).when( repo ).getFile( anyString() );
Mockito.doAnswer( answerRepoGetFile ).when( repo ).getFile( anyString(), Mockito.anyBoolean() );
Mockito.doAnswer( answerRepoGetFile ).when( repo ).getFile( anyString(), Mockito.anyBoolean(),
any( IPentahoLocale.class ) );
Mockito.doAnswer( answerRepoGetFile ).when( repo ).getFile( anyString(), any( IPentahoLocale.class ) );
Mockito.doAnswer( answerRepoGetFile ).when( repo ).getFileById( any( Serializable.class ) );
Mockito.doAnswer( answerRepoGetFile ).when( repo ).getFileById( any( Serializable.class ), Mockito.anyBoolean() );
Mockito.doAnswer( answerRepoGetFile ).when( repo ).getFileById( any( Serializable.class ), Mockito.anyBoolean(),
any( IPentahoLocale.class ) );
Mockito.doAnswer( answerRepoGetFile ).when( repo ).getFileById( any( Serializable.class ),
any( IPentahoLocale.class ) );
Answer<List<RepositoryFile>> answerRepoGetChildren = new Answer<List<RepositoryFile>>() {
@Override
public List<RepositoryFile> answer( InvocationOnMock invocation ) throws Throwable {
// returns the following item from <repoFiles>
RepositoryRequest r = (RepositoryRequest) invocation.getArguments()[0];
String path = r.getPath();
RepositoryFile thisFile = getRepoFile( repoFilesMap, path );
if ( thisFile == null || !thisFile.isFolder() ) {
return Collections.emptyList();
}
for ( int i = 0, n = repoFiles.length - 1; i < n; i++ ) {
RepositoryFile iFile = repoFiles[i];
if ( iFile == thisFile || iFile.getId().equals( thisFile.getId() ) ) {
return Collections.singletonList( repoFiles[i + 1] );
}
}
return Collections.emptyList();
}
};
Mockito.doAnswer( answerRepoGetChildren ).when( repo ).getChildren( any( RepositoryRequest.class ) );
doReturn( availableLocales ).when( repo ).getAvailableLocalesForFile( Mockito.any( RepositoryFile.class ) );
doReturn( availableLocales ).when( repo ).getAvailableLocalesForFileById( Mockito.any( Serializable.class ) );
doReturn( availableLocales ).when( repo ).getAvailableLocalesForFileByPath( Mockito.any( String.class ) );
doReturn( localePropertries ).when( repo ).getLocalePropertiesForFileById( Mockito.any( File.class ),
Mockito.anyString() );
RepositoryFileSid sid = mock( RepositoryFileSid.class );
doReturn( "testUser" ).when( sid ).getName();
doReturn( Type.USER ).when( sid ).getType();
final RepositoryFileAcl mockAcl = mock( RepositoryFileAcl.class );
doReturn( sid ).when( mockAcl ).getOwner();
doReturn( mockAcl ).when( repo ).getAcl( any( Serializable.class ) );
Answer<IRepositoryFileData> answerGetDataForRead = new Answer<IRepositoryFileData>() {
@Override
public IRepositoryFileData answer( InvocationOnMock invocation ) throws Throwable {
Serializable id = (Serializable) invocation.getArguments()[0];
RepositoryFile file = getRepoFile( repoFilesMap, id );
if ( !file.isFolder() ) {
return new SimpleRepositoryFileData( new ByteArrayInputStream( new byte[0] ), "UTF-8", MIME_PRPT.getName() );
}
return null;
}
};
doAnswer( answerGetDataForRead ).when( repo ).getDataForRead( Mockito.any( Serializable.class ),
Mockito.any( Class.class ) );
exportHandler = new DefaultExportHandler();
defaultConverter = new StreamConverter( repo );
PentahoSystem.clearObjectFactory();
microPlatform = new MicroPlatform( getSolutionPath() );
microPlatform.defineInstance( IUnifiedRepository.class, repo );
// microPlatform.defineInstance( IPlatformMimeResolver.class, mimeResolver );
microPlatform.defineInstance( ISolutionEngine.class, Mockito.mock( SolutionEngine.class ) );
microPlatform.defineInstance( IDatasourceMgmtService.class, Mockito.mock( IDatasourceMgmtService.class ) );
microPlatform.start();
exportSession = new StandaloneSession();
PentahoSessionHolder.setStrategyName( PentahoSessionHolder.MODE_GLOBAL );
PentahoSessionHolder.setSession( exportSession );
}
private String getSolutionPath() {
return tempDir.getPath();
}
@After
public void cleanup() throws Exception {
PentahoSessionHolder.removeSession();
if ( microPlatform != null ) {
microPlatform.stop();
}
}
private RepositoryFile getRepoFile( final Map<Serializable, RepositoryFile> repoFilesMap, final Object fileId ) {
RepositoryFile result = repoFilesMap.get( fileId );
if ( result == null && fileId instanceof String ) {
String sFileId = (String) fileId;
if ( sFileId.endsWith( "/" ) || sFileId.endsWith( "\\" ) ) {
sFileId = sFileId.substring( 0, sFileId.length() - 1 );
} else {
sFileId = sFileId + "/";
}
result = repoFilesMap.get( sFileId );
if ( !result.isFolder() ) {
result = null;
}
}
return result;
}
@Test
public void testGetFixedZipEntryName() {
IUnifiedRepository repo = mock( IUnifiedRepository.class );
final String fileName = "eval (+)%.prpt";
final String filePath = "/home/test user/two words/eval (+)%.prpt";
String processorBasePath = "/home/test user";
ZipExportProcessor zipMF = new ZipExportProcessor( processorBasePath, repo, true );
ZipExportProcessor zipNoMF = new ZipExportProcessor( processorBasePath, repo, false );
String basePath = "/home/test user";
String simpleZipEntryName = "two words/eval (+)%.prpt";
String encodedZipEntryName = "two+words/eval+%28%2B%29%25.prpt";
RepositoryFile file = null;
file = new RepositoryFile.Builder( fileName ).path( filePath ).folder( false ).build();
assertEquals( simpleZipEntryName, zipNoMF.getFixedZipEntryName( file, basePath ) );
assertEquals( encodedZipEntryName, zipMF.getFixedZipEntryName( file, basePath ) );
file = new RepositoryFile.Builder( fileName ).path( filePath ).folder( true ).build();
assertEquals( simpleZipEntryName + "/", zipNoMF.getFixedZipEntryName( file, basePath ) );
assertEquals( encodedZipEntryName + "/", zipMF.getFixedZipEntryName( file, basePath ) );
basePath = "/";
simpleZipEntryName = "home/test user/two words/eval (+)%.prpt";
encodedZipEntryName = "home/test+user/two+words/eval+%28%2B%29%25.prpt";
file = new RepositoryFile.Builder( fileName ).path( filePath ).folder( false ).build();
assertEquals( simpleZipEntryName, zipNoMF.getFixedZipEntryName( file, basePath ) );
assertEquals( encodedZipEntryName, zipMF.getFixedZipEntryName( file, basePath ) );
file = new RepositoryFile.Builder( fileName ).path( filePath ).folder( true ).build();
assertEquals( simpleZipEntryName + "/", zipNoMF.getFixedZipEntryName( file, basePath ) );
assertEquals( encodedZipEntryName + "/", zipMF.getFixedZipEntryName( file, basePath ) );
basePath = "";
simpleZipEntryName = "home/test user/two words/eval (+)%.prpt";
encodedZipEntryName = "home/test+user/two+words/eval+%28%2B%29%25.prpt";
file = new RepositoryFile.Builder( fileName ).path( filePath ).folder( false ).build();
assertEquals( simpleZipEntryName, zipNoMF.getFixedZipEntryName( file, basePath ) );
assertEquals( encodedZipEntryName, zipMF.getFixedZipEntryName( file, basePath ) );
file = new RepositoryFile.Builder( fileName ).path( filePath ).folder( true ).build();
assertEquals( simpleZipEntryName + "/", zipNoMF.getFixedZipEntryName( file, basePath ) );
assertEquals( encodedZipEntryName + "/", zipMF.getFixedZipEntryName( file, basePath ) );
}
@Test
public void testPerformExport_withoutManifest() throws Exception {
String expFolderPath = "/home/test user/two words/";
ZipExportProcessor zipNoMF = new ZipExportProcessor( expFolderPath, repo, false );
exportHandler.setConverters( assignConverterForExt( defaultConverter, "prpt" ) );
zipNoMF.addExportHandler( exportHandler );
RepositoryFile expFolder = repo.getFile( expFolderPath );
assertNotNull( expFolder );
File result = zipNoMF.performExport( repo.getFile( expFolderPath ) );
Set<String> zipEntriesFiles = extractZipEntries( result );
final String[] expectedEntries =
new String[] { "two words/eval (+)%.prpt", "two words/eval (+)%.prpt_en.locale", "two words/index_en.locale" };
for ( String e : expectedEntries ) {
assertTrue( "expected entry: [" + e + "]", zipEntriesFiles.contains( e ) );
}
assertEquals( "entries count", expectedEntries.length, zipEntriesFiles.size() );
}
@Test
public void testPerformExport_withManifest() throws Exception {
String expFolderPath = "/home/test user/two words/";
ZipExportProcessor zipMF = new ZipExportProcessor( expFolderPath, repo, true );
exportHandler.setConverters( assignConverterForExt( defaultConverter, "prpt" ) );
zipMF.addExportHandler( exportHandler );
RepositoryFile expFolder = repo.getFile( expFolderPath );
assertNotNull( expFolder );
File result = zipMF.performExport( repo.getFile( expFolderPath ) );
Set<String> zipEntriesFiles = extractZipEntries( result );
final String[] expectedEntries =
new String[] { "two+words/eval+%28%2B%29%25.prpt", "two+words/eval+%28%2B%29%25.prpt_en.locale",
"two+words/index_en.locale", "exportManifest.xml" };
for ( String e : expectedEntries ) {
assertTrue( "expected entry: [" + e + "]", zipEntriesFiles.contains( e ) );
}
assertEquals( "entries count", expectedEntries.length, zipEntriesFiles.size() );
}
private Map<String, Converter> assignConverterForExt( Converter conv, String... exts ) {
final Map<String, Converter> converters = new HashMap<String, Converter>();
for ( String ext : exts ) {
converters.put( ext, conv );
}
return converters;
}
private Set<String> extractZipEntries( File zipFile ) throws IOException {
Set<String> result = new HashSet<String>();
FileInputStream fis = null;
ZipInputStream zis = null;
try {
fis = new FileInputStream( zipFile );
zis = new ZipInputStream( fis );
for ( ZipEntry entry = zis.getNextEntry(); entry != null; entry = zis.getNextEntry() ) {
if ( !entry.isDirectory() ) {
final String entityName = entry.getName().replaceAll( "\\\\", "/" );
result.add( entityName );
}
}
} finally {
if ( fis != null ) {
fis.close();
}
}
return result;
}
}