/*!
* 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-2016 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.platform.plugin.services.importer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
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.engine.PluginBeanException;
import org.pentaho.platform.api.mimetype.IMimeType;
import org.pentaho.platform.api.mimetype.IPlatformMimeResolver;
import org.pentaho.platform.api.repository.datasource.IDatasourceMgmtService;
import org.pentaho.platform.api.repository2.unified.Converter;
import org.pentaho.platform.api.repository2.unified.IPlatformImportBundle;
import org.pentaho.platform.api.repository2.unified.IRepositoryContentConverterHandler;
import org.pentaho.platform.api.repository2.unified.IUnifiedRepository;
import org.pentaho.platform.api.repository2.unified.RepositoryFile;
import org.pentaho.platform.api.repository2.unified.data.simple.SimpleRepositoryFileData;
import org.pentaho.platform.core.mimetype.MimeType;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.engine.core.system.boot.PlatformInitializationException;
import org.pentaho.platform.engine.services.solution.SolutionEngine;
import org.pentaho.platform.plugin.services.importexport.Log4JRepositoryImportLogger;
import org.pentaho.platform.plugin.services.importexport.StreamConverter;
import org.pentaho.platform.repository2.unified.fs.FileSystemBackedUnifiedRepository;
import org.pentaho.test.platform.engine.core.MicroPlatform;
import org.pentaho.test.platform.utils.TestResourceLocation;
/**
*
* Checks that SolutionImportHandler processes file names correctly
*
*/
public class SolutionImportHandlerNamingIT extends Assert {
private static final String SOLUTION_PATH = TestResourceLocation.TEST_RESOURCES + "/SolutionImportHandlerNamingTest";
private static final String REPO_PATH = "/repo";
private static final String IMPORT_REPO_DIR = "/home/testuser";
private static final String DEFAULT_ENCODING = "UTF-8";
private static final String SRC_ROOT = "/exported";
private static final String SRC_EXPORTMANIFEST = SRC_ROOT + "/exportManifest.xml";
private static final String SRC_CONTENT_FILE = SRC_ROOT + "/content.prpt";
private static final String ZIPENTRY_EXPORTMANIFEST = "exportManifest.xml";
private static final String ZIPENTRY_CONTENT_FILE = "two+words%2525/eval+%28%2B%29%2525.prpt";
private static final String ZIPENTRY_CONTENT_FILE_NOMANIFEST = "two words%25/eval (+)%25.prpt";
private final IMimeType MIME_SOLUTION = new MimeType( "application/vnd.pentaho.solution-repository", "zip" );
private final IMimeType MIME_PRPT = new MimeType( "text/prptMimeType", "prpt" );
private final IMimeType MIME_XML = new MimeType( "text/xml", "xml" );
private static File tempDir;
private static File solutionDir;
private SolutionImportHandler solutionImportHandler;
private File repoRoot;
private FileSystemBackedUnifiedRepository repo;
private MicroPlatform microPlatform;
private AnswerTest<RepositoryFile> answer;
// private PentahoPlatformImporter importer;
@BeforeClass
public static void beforeClass() throws IOException {
tempDir = File.createTempFile( "test", null );
tempDir.delete();
tempDir.mkdir();
FileUtils.forceDeleteOnExit( tempDir );
solutionDir = new File( getSolutionPath() );
assertTrue( solutionDir.exists() );
assertTrue( solutionDir.isDirectory() );
}
@AfterClass
public static void afterClass() throws IOException {
FileUtils.forceDelete( tempDir );
}
@Before
public void init() throws IOException, PlatformInitializationException, PlatformImportException,
DomainIdNullException, DomainAlreadyExistsException, DomainStorageException {
// repository
File repoDir = new File( tempDir.getAbsolutePath() + REPO_PATH );
FileUtils.forceMkdir( repoDir );
FileUtils.cleanDirectory( repoDir );
repoRoot = repoDir;
repo = new FileSystemBackedUnifiedRepository();
repo = Mockito.spy( repo );
repo.setRootDir( repoRoot );
// mimeResolver
final Converter defaultConverter = new StreamConverter();
final List<IMimeType> solutionMimeList = java.util.Collections.singletonList( MIME_SOLUTION );
final List<IMimeType> contentMimeList = java.util.Arrays.asList( new IMimeType[] { MIME_PRPT, MIME_XML } );
final List<IMimeType> allMimeTypes = new ArrayList<IMimeType>( solutionMimeList.size() + contentMimeList.size() );
{
allMimeTypes.addAll( solutionMimeList );
allMimeTypes.addAll( contentMimeList );
for ( IMimeType mimeType : allMimeTypes ) {
mimeType.setConverter( defaultConverter );
}
}
final IPlatformMimeResolver mimeResolver = new NameBaseMimeResolver();
for ( IMimeType mimeType : allMimeTypes ) {
mimeResolver.addMimeType( mimeType );
}
// platform, import handlers
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 ) );
IRepositoryContentConverterHandler converterHandler =
new DefaultRepositoryContentConverterHandler( new HashMap<String, Converter>() );
RepositoryFileImportFileHandler contentImportFileHandler = new RepositoryFileImportFileHandler( contentMimeList );
contentImportFileHandler.setRepository( repo );
solutionImportHandler = new SolutionImportHandler( solutionMimeList );
List<IPlatformImportHandler> handlers = new ArrayList<IPlatformImportHandler>();
handlers.add( contentImportFileHandler );
handlers.add( solutionImportHandler );
PentahoPlatformImporter importer = new PentahoPlatformImporter( handlers, converterHandler );
importer.setDefaultHandler( contentImportFileHandler );
importer.setRepositoryImportLogger( new Log4JRepositoryImportLogger() );
microPlatform.defineInstance( IPlatformImporter.class, importer );
microPlatform.start();
}
@After
public void cleanup() throws PluginBeanException, PlatformInitializationException, IOException {
microPlatform.stop();
FileUtils.cleanDirectory( repoRoot );
}
private static String getSolutionPath() {
return SOLUTION_PATH;
}
private void assertFileContentEquals( File expected, RepositoryFile actual ) throws IOException {
FileInputStream inExp = null;
InputStream inAct = null;
try {
inExp = new FileInputStream( expected );
try {
inAct = repo.getDataForRead( actual.getId(), SimpleRepositoryFileData.class ).getInputStream();
final String msg = "src{ " + expected.getName() + " } == repo{ " + actual.getName() + " }";
assertTrue( msg, IOUtils.contentEquals( inExp, inAct ) );
} finally {
if ( inAct != null ) {
inAct.close();
}
}
} finally {
if ( inExp != null ) {
inExp.close();
}
}
}
/**
* Import has to be launched in a separate thread because importSession is thread-local.
*
* @param solutionImportHandler
* @param bundle
* @throws InterruptedException
*/
private void runImport( final SolutionImportHandler solutionImportHandler, final IPlatformImportBundle bundle )
throws InterruptedException {
Thread thr = new Thread() {
@Override
public void run() {
try {
solutionImportHandler.importFile( bundle );
} catch ( Error | RuntimeException e ) {
e.printStackTrace();
throw e;
} catch ( Exception e ) {
e.printStackTrace();
throw new RuntimeException( e );
}
}
};
thr.start();
thr.join( 0L );
}
@Test
public void testImportFileWithoutManifest() throws PlatformImportException, DomainIdNullException,
DomainAlreadyExistsException, DomainStorageException, IOException, PlatformInitializationException,
InterruptedException {
ByteArrayInputStream solutionInputStream = null;
final File srcContentFile = new File( solutionDir + SRC_CONTENT_FILE );
ByteArrayOutputStream tmpOS = null;
try {
tmpOS = new ByteArrayOutputStream();
ZipOutputStream zipOS = new ZipOutputStream( tmpOS );
zipOS.putNextEntry( new ZipEntry( ZIPENTRY_CONTENT_FILE_NOMANIFEST ) );
FileUtils.copyFile( srcContentFile, zipOS );
zipOS.closeEntry();
zipOS.close();
solutionInputStream = new ByteArrayInputStream( tmpOS.toByteArray() );
} finally {
if ( tmpOS != null ) {
tmpOS.close();
}
}
final IPlatformImportBundle bundle = buildBundle( solutionInputStream );
runImport( solutionImportHandler, bundle );
{ // files with correct names do exist
RepositoryFile dir = repo.getFile( IMPORT_REPO_DIR + "/two words%25" );
assertNotNull( dir );
assertTrue( dir.isFolder() );
RepositoryFile file = repo.getFile( IMPORT_REPO_DIR + "/two words%25/eval (+)%25.prpt" );
assertNotNull( file );
assertFalse( file.isFolder() );
assertFileContentEquals( srcContentFile, file );
}
{ // files with incorrect names do not exist
assertNull( repo.getFile( IMPORT_REPO_DIR + "/two+words%2525" ) );
assertNull( repo.getFile( IMPORT_REPO_DIR + "/two+words%25" ) );
assertNull( repo.getFile( IMPORT_REPO_DIR + "/two+words%" ) );
assertNull( repo.getFile( IMPORT_REPO_DIR + "/two words%2525" ) );
assertNull( repo.getFile( IMPORT_REPO_DIR + "/two words%" ) );
assertNull( repo.getFile( IMPORT_REPO_DIR + "/two words%25/eval+%28%2B%29%2525.prpt" ) );
assertNull( repo.getFile( IMPORT_REPO_DIR + "/two words%25/eval (+)%.prpt" ) );
}
}
@Test
public void testImportFileWithManifest() throws PlatformImportException, DomainIdNullException,
DomainAlreadyExistsException, DomainStorageException, IOException, PlatformInitializationException,
InterruptedException {
ZipBuilder zipBuilder = new ZipBuilder();
zipBuilder.addFile( solutionDir + SRC_EXPORTMANIFEST, ZIPENTRY_EXPORTMANIFEST );
zipBuilder.addFile( solutionDir + SRC_CONTENT_FILE, ZIPENTRY_CONTENT_FILE );
ByteArrayOutputStream out = zipBuilder.build();
ByteArrayInputStream solutionInputStream = new ByteArrayInputStream( out.toByteArray() );
final IPlatformImportBundle bundle = buildBundle( solutionInputStream );
runImport( solutionImportHandler, bundle );
{ // files with correct names do exist
RepositoryFile dir = repo.getFile( IMPORT_REPO_DIR + "/two words%25" );
assertNotNull( dir );
assertTrue( dir.isFolder() );
RepositoryFile file = repo.getFile( IMPORT_REPO_DIR + "/two words%25/eval (+)%25.prpt" );
assertNotNull( file );
assertFalse( file.isFolder() );
assertFileContentEquals( new File( solutionDir + SRC_CONTENT_FILE ), file );
}
{ // exportManifest.xml does not exist
assertNull( repo.getFile( IMPORT_REPO_DIR + "/exportManifest.xml" ) );
}
{ // files with incorrect names do not exist
assertNull( repo.getFile( IMPORT_REPO_DIR + "/two+words%2525" ) );
assertNull( repo.getFile( IMPORT_REPO_DIR + "/two+words%25" ) );
assertNull( repo.getFile( IMPORT_REPO_DIR + "/two+words%" ) );
assertNull( repo.getFile( IMPORT_REPO_DIR + "/two words%2525" ) );
assertNull( repo.getFile( IMPORT_REPO_DIR + "/two words%" ) );
assertNull( repo.getFile( IMPORT_REPO_DIR + "/two words%25/eval+%28%2B%29%2525.prpt" ) );
assertNull( repo.getFile( IMPORT_REPO_DIR + "/two words%25/eval (+)%.prpt" ) );
}
}
private static class ZipBuilder {
private ByteArrayOutputStream out = new ByteArrayOutputStream();
private ZipOutputStream zip = new ZipOutputStream( out );
public void addFile( String filePath, String fileName ) throws IOException {
final File file = new File( filePath );
assertTrue( file.getAbsolutePath() + " didn't found.", file.exists() );
zip.putNextEntry( new ZipEntry( fileName ) );
FileUtils.copyFile( file, zip );
zip.closeEntry();
}
public ByteArrayOutputStream build() throws IOException {
zip.close();
out.close();
return out;
}
}
private IPlatformImportBundle buildBundle( InputStream solutionInputStream ) {
final RepositoryFileImportBundle.Builder bundleBuilder = new RepositoryFileImportBundle.Builder();
bundleBuilder.input( solutionInputStream );
bundleBuilder.charSet( DEFAULT_ENCODING );
bundleBuilder.hidden( RepositoryFile.HIDDEN_BY_DEFAULT );
bundleBuilder.schedulable( RepositoryFile.SCHEDULABLE_BY_DEFAULT );
bundleBuilder.path( IMPORT_REPO_DIR );
bundleBuilder.overwriteFile( false );
bundleBuilder.applyAclSettings( false );
bundleBuilder.overwriteAclSettings( false );
bundleBuilder.retainOwnership( true );
bundleBuilder.name( "testSrc.zip" );
bundleBuilder.mime( "application/vnd.pentaho.solution-repository" );
return bundleBuilder.build();
}
@Test
public void testImportHiddenFileByManifest() throws IOException, InterruptedException {
repo.deleteFile( SRC_CONTENT_FILE, "" );
answer = new AnswerTest<RepositoryFile>() {
@Override
public RepositoryFile answer( InvocationOnMock invocation ) throws Throwable {
obj = invocation.getArgumentAt( 1, RepositoryFile.class );
return null;
}
};
Mockito.doAnswer( answer ).when( repo ).createFile( Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any() );
// isHidden="true" isSchedulable="true"
RepositoryFile file = importFile( "/manifestFileHidden.xml" );
assertTrue( file.isHidden() );
assertTrue( file.isSchedulable() );
// isHidden="false" isSchedulable="false"
file = importFile( "/manifestFileNotHidden.xml" );
assertFalse( file.isHidden() );
assertFalse( file.isSchedulable() );
// nothing
file = importFile( "/manifestHiddenByDefault.xml" );
assertEquals( file.isHidden(), RepositoryFile.HIDDEN_BY_DEFAULT );
assertEquals( file.isSchedulable(), RepositoryFile.SCHEDULABLE_BY_DEFAULT );
}
private RepositoryFile importFile( String manifest ) throws IOException,
InterruptedException {
ZipBuilder zipBuilder = new ZipBuilder();
zipBuilder.addFile( solutionDir + SRC_ROOT + manifest, "exportManifest.xml" );
zipBuilder.addFile( solutionDir + SRC_CONTENT_FILE, SRC_CONTENT_FILE );
ByteArrayOutputStream out = zipBuilder.build();
ByteArrayInputStream solutionInputStream = new ByteArrayInputStream( out.toByteArray() );
IPlatformImportBundle bundle = buildBundle( solutionInputStream );
runImport( solutionImportHandler, bundle );
return answer != null ? answer.getObj() : null;
}
private abstract static class AnswerTest<T> implements Answer<T> {
protected T obj;
public T getObj() {
return obj;
}
}
}