/*!
* Copyright 2010 - 2016 Pentaho 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 org.pentaho.di.repository.pur;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import javax.jcr.Repository;
import javax.xml.parsers.SAXParserFactory;
import org.apache.commons.io.FileUtils;
import org.apache.commons.vfs2.FileObject;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.pentaho.di.cluster.ClusterSchema;
import org.pentaho.di.cluster.SlaveServer;
import org.pentaho.di.core.KettleEnvironment;
import org.pentaho.di.core.NotePadMeta;
import org.pentaho.di.core.ProgressMonitorListener;
import org.pentaho.di.core.database.DatabaseMeta;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.logging.KettleLogStore;
import org.pentaho.di.core.logging.KettleLoggingEvent;
import org.pentaho.di.core.logging.KettleLoggingEventListener;
import org.pentaho.di.core.logging.LogLevel;
import org.pentaho.di.core.plugins.JobEntryPluginType;
import org.pentaho.di.core.plugins.StepPluginType;
import org.pentaho.di.core.vfs.KettleVFS;
import org.pentaho.di.imp.ImportRules;
import org.pentaho.di.imp.rule.ImportRuleInterface;
import org.pentaho.di.imp.rules.TransformationHasANoteImportRule;
import org.pentaho.di.job.JobMeta;
import org.pentaho.di.partition.PartitionSchema;
import org.pentaho.di.repository.IRepositoryExporter;
import org.pentaho.di.repository.ObjectId;
import org.pentaho.di.repository.ObjectRevision;
import org.pentaho.di.repository.RepositoryDirectoryInterface;
import org.pentaho.di.repository.RepositoryElementInterface;
import org.pentaho.di.repository.RepositoryObjectType;
import org.pentaho.di.repository.RepositoryTestBase;
import org.pentaho.di.repository.UserInfo;
import org.pentaho.di.repository.pur.metastore.MetaStoreTestBase;
import org.pentaho.di.shared.SharedObjectInterface;
import org.pentaho.di.trans.TransMeta;
import org.pentaho.di.trans.step.StepMeta;
import org.pentaho.di.trans.steps.tableinput.TableInputMeta;
import org.pentaho.metastore.api.IMetaStore;
import org.pentaho.metastore.api.IMetaStoreAttribute;
import org.pentaho.metastore.api.IMetaStoreElement;
import org.pentaho.metastore.api.IMetaStoreElementType;
import org.pentaho.metastore.api.exceptions.MetaStoreDependenciesExistsException;
import org.pentaho.metastore.api.exceptions.MetaStoreException;
import org.pentaho.metastore.api.exceptions.MetaStoreNamespaceExistsException;
import org.pentaho.metastore.util.PentahoDefaults;
import org.pentaho.platform.api.engine.IAuthorizationPolicy;
import org.pentaho.platform.api.engine.IPentahoSession;
import org.pentaho.platform.api.engine.security.userroledao.IPentahoRole;
import org.pentaho.platform.api.engine.security.userroledao.IPentahoUser;
import org.pentaho.platform.api.engine.security.userroledao.IUserRoleDao;
import org.pentaho.platform.api.mt.ITenant;
import org.pentaho.platform.api.mt.ITenantManager;
import org.pentaho.platform.api.mt.ITenantedPrincipleNameResolver;
import org.pentaho.platform.api.repository2.unified.IBackingRepositoryLifecycleManager;
import org.pentaho.platform.api.repository2.unified.IRepositoryVersionManager;
import org.pentaho.platform.api.repository2.unified.IUnifiedRepository;
import org.pentaho.platform.api.repository2.unified.RepositoryFile;
import org.pentaho.platform.core.mt.Tenant;
import org.pentaho.platform.engine.core.system.PentahoSessionHolder;
import org.pentaho.platform.engine.core.system.StandaloneSession;
import org.pentaho.platform.repository2.ClientRepositoryPaths;
import org.pentaho.platform.repository2.unified.IRepositoryFileDao;
import org.pentaho.platform.repository2.unified.ServerRepositoryPaths;
import org.pentaho.platform.repository2.unified.jcr.JcrRepositoryFileUtils;
import org.pentaho.platform.repository2.unified.jcr.RepositoryFileProxyFactory;
import org.pentaho.platform.repository2.unified.jcr.SimpleJcrTestUtils;
import org.pentaho.platform.repository2.unified.jcr.jackrabbit.security.TestPrincipalProvider;
import org.pentaho.platform.repository2.unified.jcr.sejcr.CredentialsStrategy;
import org.pentaho.platform.security.policy.rolebased.IRoleAuthorizationPolicyRoleBindingDao;
import org.pentaho.platform.security.userroledao.DefaultTenantedPrincipleNameResolver;
import org.pentaho.test.platform.engine.core.MicroPlatform;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.extensions.jcr.JcrCallback;
import org.springframework.extensions.jcr.JcrTemplate;
import org.springframework.extensions.jcr.SessionFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestContextManager;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ext.DefaultHandler2;
@ContextConfiguration( locations = { "classpath:/repository.spring.xml",
"classpath:/repository-test-override.spring.xml" } )
public class PurRepositoryIT extends RepositoryTestBase implements ApplicationContextAware, java.io.Serializable {
static final long serialVersionUID = 2064159405078106703L; /* EESOURCE: UPDATE SERIALVERUID */
private IUnifiedRepository repo;
private ITenantedPrincipleNameResolver userNameUtils = new DefaultTenantedPrincipleNameResolver();
private ITenantedPrincipleNameResolver roleNameUtils = new DefaultTenantedPrincipleNameResolver(
DefaultTenantedPrincipleNameResolver.ALTERNATE_DELIMETER );
private ITenantManager tenantManager;
private ITenant systemTenant;
private IRoleAuthorizationPolicyRoleBindingDao roleBindingDaoTarget;
private String repositoryAdminUsername;
private JcrTemplate testJcrTemplate;
private MicroPlatform mp;
IUserRoleDao testUserRoleDao;
IUserRoleDao userRoleDao;
private String singleTenantAdminRoleName;
private String tenantAuthenticatedRoleName;
private String sysAdminUserName;
private String superAdminRoleName;
private TransactionTemplate txnTemplate;
private IRepositoryFileDao repositoryFileDao;
private final String TENANT_ID_ACME = "acme";
private IBackingRepositoryLifecycleManager repositoryLifecyleManager;
private final String TENANT_ID_DUFF = "duff";
private static IAuthorizationPolicy authorizationPolicy;
private TestContextManager testContextManager;
public PurRepositoryIT( Boolean lazyRepo ) {
super( lazyRepo );
}
// ~ Methods =========================================================================================================
@BeforeClass
public static void setUpClass() throws Exception {
System.out.println( "Repository: "
+ PurRepositoryIT.class.getClassLoader().getResource( "repository.spring.xml" ).getPath() );
// folder cannot be deleted at teardown shutdown hooks have not yet necessarily completed
// parent folder must match jcrRepository.homeDir bean property in repository-test-override.spring.xml
FileUtils.deleteDirectory( new File( "/tmp/jackrabbit-test-TRUNK" ) );
PentahoSessionHolder.setStrategyName( PentahoSessionHolder.MODE_GLOBAL );
}
@AfterClass
public static void tearDownClass() throws Exception {
PentahoSessionHolder.setStrategyName( PentahoSessionHolder.MODE_INHERITABLETHREADLOCAL );
}
@Before
public void setUp() throws Exception {
this.testContextManager = new TestContextManager( getClass() );
this.testContextManager.prepareTestInstance( this );
IRepositoryVersionManager mockRepositoryVersionManager = mock( IRepositoryVersionManager.class );
when( mockRepositoryVersionManager.isVersioningEnabled( anyString() ) ).thenReturn( true );
when( mockRepositoryVersionManager.isVersionCommentEnabled( anyString() ) ).thenReturn( false );
JcrRepositoryFileUtils.setRepositoryVersionManager( mockRepositoryVersionManager );
loginAsRepositoryAdmin();
SimpleJcrTestUtils.deleteItem( testJcrTemplate, ServerRepositoryPaths.getPentahoRootFolderPath() );
mp = new MicroPlatform();
// used by DefaultPentahoJackrabbitAccessControlHelper
mp.defineInstance( "tenantedUserNameUtils", userNameUtils );
mp.defineInstance( "tenantedRoleNameUtils", roleNameUtils );
mp.defineInstance( IAuthorizationPolicy.class, authorizationPolicy );
mp.defineInstance( ITenantManager.class, tenantManager );
mp.defineInstance( "roleAuthorizationPolicyRoleBindingDaoTarget", roleBindingDaoTarget );
mp.defineInstance( "repositoryAdminUsername", repositoryAdminUsername );
mp.defineInstance( "RepositoryFileProxyFactory",
new RepositoryFileProxyFactory( testJcrTemplate, repositoryFileDao ) );
mp.defineInstance( "useMultiByteEncoding", new Boolean( false ) );
// Start the micro-platform
mp.start();
loginAsRepositoryAdmin();
setAclManagement();
systemTenant =
tenantManager.createTenant( null, ServerRepositoryPaths.getPentahoRootFolderName(), singleTenantAdminRoleName,
tenantAuthenticatedRoleName, "Anonymous" );
userRoleDao.createUser( systemTenant, sysAdminUserName, "password", "", new String[] { singleTenantAdminRoleName } );
logout();
super.setUp();
KettleEnvironment.init();
// programmatically register plugins, annotation based plugins do not get loaded unless
// they are in kettle's plugins folder.
JobEntryPluginType.getInstance().registerCustom( JobEntryAttributeTesterJobEntry.class, "test",
"JobEntryAttributeTester", "JobEntryAttributeTester", "JobEntryAttributeTester", "" );
StepPluginType.getInstance().registerCustom( TransStepAttributeTesterTransStep.class, "test",
"StepAttributeTester", "StepAttributeTester", "StepAttributeTester", "" );
repositoryMeta = new PurRepositoryMeta();
repositoryMeta.setName( "JackRabbit" );
repositoryMeta.setDescription( "JackRabbit test repository" );
userInfo = new UserInfo( EXP_LOGIN, "password", EXP_USERNAME, "Apache Tomcat user", true );
repository = new PurRepository();
repository.init( repositoryMeta );
login( sysAdminUserName, systemTenant, new String[] { singleTenantAdminRoleName, tenantAuthenticatedRoleName } );
ITenant tenantAcme =
tenantManager.createTenant( systemTenant, EXP_TENANT, singleTenantAdminRoleName, tenantAuthenticatedRoleName,
"Anonymous" );
userRoleDao.createUser( tenantAcme, EXP_LOGIN, "password", "", new String[] { singleTenantAdminRoleName } );
logout();
setUpUser();
PurRepository purRep = (PurRepository) repository;
purRep.setPurRepositoryConnector( new PurRepositoryConnector( purRep, (PurRepositoryMeta) repositoryMeta, purRep
.getRootRef() ) );
( (PurRepository) repository ).setTest( repo );
repository.connect( EXP_LOGIN, "password" );
login( EXP_LOGIN, tenantAcme, new String[] { singleTenantAdminRoleName, tenantAuthenticatedRoleName } );
System.out.println( "PUR NAME!!!: " + repo.getClass().getCanonicalName() );
RepositoryFile repositoryFile = repo.getFile( ClientRepositoryPaths.getPublicFolderPath() );
Serializable repositoryFileId = repositoryFile.getId();
List<RepositoryFile> files = repo.getChildren( repositoryFileId );
StringBuilder buf = new StringBuilder();
for ( RepositoryFile file : files ) {
buf.append( "\n" ).append( file );
}
assertTrue( "files not deleted: " + buf, files.isEmpty() );
}
private void setAclManagement() {
JcrCallback callback = PurRepositoryTestingUtils.setAclManagementCallback();
testJcrTemplate.execute( callback );
}
protected void setUpUser() {
StandaloneSession pentahoSession = new StandaloneSession( userInfo.getLogin() );
pentahoSession.setAuthenticated( userInfo.getLogin() );
pentahoSession.setAttribute( IPentahoSession.TENANT_ID_KEY, "/pentaho/" + EXP_TENANT );
List<GrantedAuthority> authorities = new ArrayList<>( 2 );
authorities.add( new SimpleGrantedAuthority( "Authenticated" ) );
authorities.add( new SimpleGrantedAuthority( "acme_Authenticated" ) );
final String password = "ignored"; //$NON-NLS-1$
UserDetails userDetails = new User( userInfo.getLogin(), password, true, true, true, true, authorities );
Authentication authentication = new UsernamePasswordAuthenticationToken( userDetails, password, authorities );
// next line is copy of SecurityHelper.setPrincipal
pentahoSession.setAttribute( "SECURITY_PRINCIPAL", authentication );
SecurityContextHolder.setStrategyName( SecurityContextHolder.MODE_GLOBAL );
PurRepositoryTestingUtils.setSession( pentahoSession, authentication );
repositoryLifecyleManager.newTenant();
repositoryLifecyleManager.newUser();
}
protected void loginAsRepositoryAdmin() {
StandaloneSession repositoryAdminSession = PurRepositoryTestingUtils.createSession( repositoryAdminUsername );
Authentication repositoryAdminAuthentication = PurRepositoryTestingUtils.createAuthentication(
repositoryAdminUsername, superAdminRoleName );
PurRepositoryTestingUtils.setSession( repositoryAdminSession, repositoryAdminAuthentication );
}
protected void logout() {
PentahoSessionHolder.removeSession();
SecurityContextHolder.getContext().setAuthentication( null );
}
protected void loginAsTenantAdmin() {
StandaloneSession pentahoSession = new StandaloneSession( "joe" );
pentahoSession.setAuthenticated( "joe" );
pentahoSession.setAttribute( IPentahoSession.TENANT_ID_KEY, "acme" );
final String password = "password";
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>( 3 );
authorities.add( new SimpleGrantedAuthority( "Authenticated" ) );
authorities.add( new SimpleGrantedAuthority( "acme_Authenticated" ) );
authorities.add( new SimpleGrantedAuthority( "acme_Admin" ) );
UserDetails userDetails = new User( "joe", password, true, true, true, true, authorities );
Authentication auth = new UsernamePasswordAuthenticationToken( userDetails, password, authorities );
PentahoSessionHolder.setSession( pentahoSession );
// this line necessary for Spring Security's MethodSecurityInterceptor
SecurityContextHolder.getContext().setAuthentication( auth );
repositoryLifecyleManager.newTenant();
repositoryLifecyleManager.newUser();
}
/**
* Logs in with given username.
*
* @param username
* username of user
* @param tenant
* tenant to which this user belongs
* @tenantAdmin true to add the tenant admin authority to the user's roles
*/
protected void login( final String username, final ITenant tenant, String[] roles ) {
StandaloneSession pentahoSession = new StandaloneSession( username );
pentahoSession.setAuthenticated( tenant.getId(), username );
PentahoSessionHolder.setSession( pentahoSession );
pentahoSession.setAttribute( IPentahoSession.TENANT_ID_KEY, tenant.getId() );
Authentication auth = PurRepositoryTestingUtils.createAuthentication( username, roles );
PurRepositoryTestingUtils.setSession( pentahoSession, auth );
createUserHomeFolder( tenant, username );
}
protected ITenant getTenant( String principalId, boolean isUser ) {
ITenant tenant = null;
ITenantedPrincipleNameResolver nameUtils = isUser ? userNameUtils : roleNameUtils;
if ( nameUtils != null ) {
tenant = nameUtils.getTenant( principalId );
}
if ( tenant == null || tenant.getId() == null ) {
tenant = getCurrentTenant();
}
return tenant;
}
protected ITenant getCurrentTenant() {
if ( PentahoSessionHolder.getSession() != null ) {
String tenantId = (String) PentahoSessionHolder.getSession().getAttribute( IPentahoSession.TENANT_ID_KEY );
return tenantId != null ? new Tenant( tenantId, true ) : null;
} else {
return null;
}
}
protected String getPrincipalName( String principalId, boolean isUser ) {
String principalName = null;
ITenantedPrincipleNameResolver nameUtils = isUser ? userNameUtils : roleNameUtils;
if ( nameUtils != null ) {
principalName = nameUtils.getPrincipleName( principalId );
}
return principalName;
}
protected void createUserHomeFolder( final ITenant theTenant, final String theUsername ) {
IPentahoSession origPentahoSession = PentahoSessionHolder.getSession();
Authentication origAuthentication = SecurityContextHolder.getContext().getAuthentication();
StandaloneSession pentahoSession = new StandaloneSession( repositoryAdminUsername );
pentahoSession.setAuthenticated( null, repositoryAdminUsername );
PentahoSessionHolder.setSession( pentahoSession );
String principleId = userNameUtils.getPrincipleId( theTenant, theUsername );
String authenticatedRoleId = roleNameUtils.getPrincipleId( theTenant, tenantAuthenticatedRoleName );
TransactionCallbackWithoutResult callback =
PurRepositoryTestingUtils.createUserHomeDirCallback( theTenant, theUsername, principleId, authenticatedRoleId,
repositoryFileDao );
try {
txnTemplate.execute( callback );
} finally {
// Switch our identity back to the original user.
PurRepositoryTestingUtils.setSession( origPentahoSession, origAuthentication );
}
}
private void cleanupUserAndRoles( final ITenant tenant ) {
loginAsRepositoryAdmin();
for ( IPentahoRole role : testUserRoleDao.getRoles( tenant ) ) {
testUserRoleDao.deleteRole( role );
}
for ( IPentahoUser user : testUserRoleDao.getUsers( tenant ) ) {
testUserRoleDao.deleteUser( user );
}
}
@After
public void tearDown() throws Exception {
// null out fields to get back memory
authorizationPolicy = null;
login( sysAdminUserName, systemTenant, new String[] { singleTenantAdminRoleName, tenantAuthenticatedRoleName } );
ITenant tenant =
tenantManager.getTenant( "/" + ServerRepositoryPaths.getPentahoRootFolderName() + "/" + TENANT_ID_ACME );
if ( tenant != null ) {
cleanupUserAndRoles( tenant );
}
login( sysAdminUserName, systemTenant, new String[] { singleTenantAdminRoleName, tenantAuthenticatedRoleName } );
tenant = tenantManager.getTenant( "/" + ServerRepositoryPaths.getPentahoRootFolderName() + "/" + TENANT_ID_DUFF );
if ( tenant != null ) {
cleanupUserAndRoles( tenant );
}
cleanupUserAndRoles( systemTenant );
SimpleJcrTestUtils.deleteItem( testJcrTemplate, ServerRepositoryPaths.getPentahoRootFolderPath() );
logout();
repositoryAdminUsername = null;
singleTenantAdminRoleName = null;
tenantAuthenticatedRoleName = null;
// roleBindingDao = null;
authorizationPolicy = null;
testJcrTemplate = null;
// null out fields to get back memory
tenantManager = null;
repo = null;
mp.stop();
mp = null;
}
@Override
protected void delete( ObjectId id ) {
if ( id != null ) {
repo.deleteFile( id.getId(), true, null );
}
}
/**
* Tries twice to delete files. By not failing outright on the first pass, we hopefully eliminate files that are
* holding references to the files we cannot delete.
*/
protected void safelyDeleteAll( final ObjectId[] ids ) throws Exception {
Exception firstException = null;
List<String> frozenIds = new ArrayList<String>();
for ( ObjectId id : ids ) {
frozenIds.add( id.getId() );
}
List<String> remainingIds = new ArrayList<String>();
for ( ObjectId id : ids ) {
remainingIds.add( id.getId() );
}
try {
for ( int i = 0; i < frozenIds.size(); i++ ) {
repo.deleteFile( frozenIds.get( i ), true, null );
remainingIds.remove( frozenIds.get( i ) );
}
} catch ( Exception e ) {
e.printStackTrace();
}
if ( !remainingIds.isEmpty() ) {
List<String> frozenIds2 = remainingIds;
List<String> remainingIds2 = new ArrayList<String>();
for ( String id : frozenIds2 ) {
remainingIds2.add( id );
}
try {
for ( int i = 0; i < frozenIds2.size(); i++ ) {
repo.deleteFile( frozenIds2.get( i ), true, null );
remainingIds2.remove( frozenIds2.get( i ) );
}
} catch ( Exception e ) {
if ( firstException == null ) {
firstException = e;
}
}
if ( !remainingIds2.isEmpty() ) {
throw firstException;
}
}
}
@Override
public void setApplicationContext( final ApplicationContext applicationContext ) throws BeansException {
SessionFactory jcrSessionFactory = (SessionFactory) applicationContext.getBean( "jcrSessionFactory" );
testJcrTemplate = new JcrTemplate( jcrSessionFactory );
testJcrTemplate.setAllowCreate( true );
testJcrTemplate.setExposeNativeSession( true );
repositoryAdminUsername = (String) applicationContext.getBean( "repositoryAdminUsername" );
superAdminRoleName = (String) applicationContext.getBean( "superAdminAuthorityName" );
sysAdminUserName = (String) applicationContext.getBean( "superAdminUserName" );
tenantAuthenticatedRoleName = (String) applicationContext.getBean( "singleTenantAuthenticatedAuthorityName" );
singleTenantAdminRoleName = (String) applicationContext.getBean( "singleTenantAdminAuthorityName" );
tenantManager = (ITenantManager) applicationContext.getBean( "tenantMgrProxy" );
roleBindingDaoTarget =
(IRoleAuthorizationPolicyRoleBindingDao) applicationContext
.getBean( "roleAuthorizationPolicyRoleBindingDaoTarget" );
authorizationPolicy = (IAuthorizationPolicy) applicationContext.getBean( "authorizationPolicy" );
repo = (IUnifiedRepository) applicationContext.getBean( "unifiedRepository" );
userRoleDao = (IUserRoleDao) applicationContext.getBean( "userRoleDao" );
repositoryFileDao = (IRepositoryFileDao) applicationContext.getBean( "repositoryFileDao" );
testUserRoleDao = userRoleDao;
repositoryLifecyleManager =
(IBackingRepositoryLifecycleManager) applicationContext.getBean( "defaultBackingRepositoryLifecycleManager" );
txnTemplate = (TransactionTemplate) applicationContext.getBean( "jcrTransactionTemplate" );
TestPrincipalProvider.userRoleDao = testUserRoleDao;
TestPrincipalProvider.adminCredentialsStrategy =
(CredentialsStrategy) applicationContext.getBean( "jcrAdminCredentialsStrategy" );
TestPrincipalProvider.repository = (Repository) applicationContext.getBean( "jcrRepository" );
}
@Override
protected RepositoryDirectoryInterface loadStartDirectory() throws Exception {
RepositoryDirectoryInterface rootDir = repository.loadRepositoryDirectoryTree();
RepositoryDirectoryInterface startDir = rootDir.findDirectory( "public" );
assertNotNull( startDir );
return startDir;
}
/**
* Allow PentahoSystem to create this class but it in turn delegates to the authorizationPolicy fetched from Spring's
* ApplicationContext.
*/
public static class DelegatingAuthorizationPolicy implements IAuthorizationPolicy {
public List<String> getAllowedActions( final String actionNamespace ) {
return authorizationPolicy.getAllowedActions( actionNamespace );
}
public boolean isAllowed( final String actionName ) {
return authorizationPolicy.isAllowed( actionName );
}
}
@Test
public void testNewNonAmbiguousNaming() throws Exception {
PurRepository repo = (PurRepository) repository;
System.setProperty( "KETTLE_COMPATIBILITY_PUR_OLD_NAMING_MODE", "N" );
PartitionSchema partSchema1 = createPartitionSchema( "find.me" ); //$NON-NLS-1$
PartitionSchema partSchema2 = createPartitionSchema( "find|me" ); //$NON-NLS-1$
repository.save( partSchema1, VERSION_COMMENT_V1, null );
repository.save( partSchema2, VERSION_COMMENT_V1, null );
Map<RepositoryObjectType, List<? extends SharedObjectInterface>> sharedObjectsByType =
new HashMap<RepositoryObjectType, List<? extends SharedObjectInterface>>();
repo.readSharedObjects( sharedObjectsByType, RepositoryObjectType.PARTITION_SCHEMA );
List<PartitionSchema> partitionSchemas =
(List<PartitionSchema>) sharedObjectsByType.get( RepositoryObjectType.PARTITION_SCHEMA );
assertEquals( 2, partitionSchemas.size() );
System.setProperty( "KETTLE_COMPATIBILITY_PUR_OLD_NAMING_MODE", "Y" );
PartitionSchema partSchema3 = createPartitionSchema( "another.one" ); //$NON-NLS-1$
PartitionSchema partSchema4 = createPartitionSchema( "another|one" ); //$NON-NLS-1$
repository.save( partSchema3, VERSION_COMMENT_V1, null );
repository.save( partSchema4, VERSION_COMMENT_V1, null );
sharedObjectsByType = new HashMap<RepositoryObjectType, List<? extends SharedObjectInterface>>();
repo.readSharedObjects( sharedObjectsByType, RepositoryObjectType.PARTITION_SCHEMA );
partitionSchemas = (List<PartitionSchema>) sharedObjectsByType.get( RepositoryObjectType.PARTITION_SCHEMA );
assertEquals( 3, partitionSchemas.size() );
}
private class MockProgressMonitorListener implements ProgressMonitorListener {
public void beginTask( String arg0, int arg1 ) {
}
public void done() {
}
public boolean isCanceled() {
return false;
}
public void setTaskName( String arg0 ) {
}
public void subTask( String arg0 ) {
}
public void worked( int arg0 ) {
}
}
private class MockRepositoryExportParser extends DefaultHandler2 {
private List<String> nodeNames = new ArrayList<String>();
private SAXParseException fatalError;
private List<String> nodesToCapture = Arrays
.asList( "repository", "transformations", "transformation", "jobs", "job" );
//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
@Override
public void startElement( String uri, String localName, String qName, Attributes attributes ) throws SAXException {
// Only capture nodes we care about
if ( nodesToCapture.contains( qName ) ) {
nodeNames.add( qName );
}
}
@Override
public void fatalError( SAXParseException e ) throws SAXException {
fatalError = e;
}
public List<String> getNodesWithName( String name ) {
List<String> nodes = new ArrayList<String>();
for ( String node : nodeNames ) {
if ( node.equals( name ) ) {
nodes.add( name );
}
}
return nodes;
}
public List<String> getNodeNames() {
return nodeNames;
}
public SAXParseException getFatalError() {
return fatalError;
}
}
@Test
public void testExport() throws Exception {
final String exportFileName = new File( "test.export" ).getAbsolutePath(); //$NON-NLS-1$
RepositoryDirectoryInterface rootDir = initRepo();
String uniqueTransName = EXP_TRANS_NAME.concat( EXP_DBMETA_NAME );
TransMeta transMeta = createTransMeta( EXP_DBMETA_NAME );
// Create a database association
DatabaseMeta dbMeta = createDatabaseMeta( EXP_DBMETA_NAME );
repository.save( dbMeta, VERSION_COMMENT_V1, null );
TableInputMeta tableInputMeta = new TableInputMeta();
tableInputMeta.setDatabaseMeta( dbMeta );
transMeta.addStep( new StepMeta( EXP_TRANS_STEP_1_NAME, tableInputMeta ) );
RepositoryDirectoryInterface transDir = rootDir.findDirectory( DIR_TRANSFORMATIONS );
repository.save( transMeta, VERSION_COMMENT_V1, null );
deleteStack.push( transMeta ); // So this transformation is cleaned up afterward
assertNotNull( transMeta.getObjectId() );
ObjectRevision version = transMeta.getObjectRevision();
assertNotNull( version );
assertTrue( hasVersionWithComment( transMeta, VERSION_COMMENT_V1 ) );
assertTrue( repository.exists( uniqueTransName, transDir, RepositoryObjectType.TRANSFORMATION ) );
JobMeta jobMeta = createJobMeta( EXP_JOB_NAME );
RepositoryDirectoryInterface jobsDir = rootDir.findDirectory( DIR_JOBS );
repository.save( jobMeta, VERSION_COMMENT_V1, null );
deleteStack.push( jobMeta );
assertNotNull( jobMeta.getObjectId() );
version = jobMeta.getObjectRevision();
assertNotNull( version );
assertTrue( hasVersionWithComment( jobMeta, VERSION_COMMENT_V1 ) );
assertTrue( repository.exists( EXP_JOB_NAME, jobsDir, RepositoryObjectType.JOB ) );
LogListener errorLogListener = new LogListener( LogLevel.ERROR );
KettleLogStore.getAppender().addLoggingEventListener( errorLogListener );
try {
repository.getExporter().exportAllObjects( new MockProgressMonitorListener(), exportFileName, null, "all" ); //$NON-NLS-1$
FileObject exportFile = KettleVFS.getFileObject( exportFileName );
assertFalse( "file left open", exportFile.getContent().isOpen() );
assertNotNull( exportFile );
MockRepositoryExportParser parser = new MockRepositoryExportParser();
SAXParserFactory.newInstance().newSAXParser().parse( KettleVFS.getInputStream( exportFile ), parser );
if ( parser.getFatalError() != null ) {
throw parser.getFatalError();
}
assertNotNull( "No nodes found in export", parser.getNodeNames() ); //$NON-NLS-1$
assertTrue( "No nodes found in export", !parser.getNodeNames().isEmpty() ); //$NON-NLS-1$
assertEquals( "Incorrect number of nodes", 5, parser.getNodeNames().size() ); //$NON-NLS-1$
assertEquals( "Incorrect number of transformations", 1, parser.getNodesWithName( "transformation" ).size() ); //$NON-NLS-1$ //$NON-NLS-2$
assertEquals( "Incorrect number of jobs", 1, parser.getNodesWithName( "job" ).size() ); //$NON-NLS-1$ //$NON-NLS-2$
assertTrue( "log error", errorLogListener.getEvents().isEmpty() );
} finally {
KettleVFS.getFileObject( exportFileName ).delete();
KettleLogStore.getAppender().removeLoggingEventListener( errorLogListener );
}
}
@Test
public void testMetaStoreBasics() throws MetaStoreException {
IMetaStore metaStore = repository.getMetaStore();
assertNotNull( metaStore );
MetaStoreTestBase base = new MetaStoreTestBase();
base.testFunctionality( metaStore );
}
@Test
public void testMetaStoreNamespaces() throws MetaStoreException {
IMetaStore metaStore = repository.getMetaStore();
assertNotNull( metaStore );
// We start with a clean slate, only the pentaho namespace
//
assertEquals( 1, metaStore.getNamespaces().size() );
String ns = PentahoDefaults.NAMESPACE;
assertEquals( true, metaStore.namespaceExists( ns ) );
metaStore.deleteNamespace( ns );
assertEquals( false, metaStore.namespaceExists( ns ) );
assertEquals( 0, metaStore.getNamespaces().size() );
metaStore.createNamespace( ns );
assertEquals( true, metaStore.namespaceExists( ns ) );
List<String> namespaces = metaStore.getNamespaces();
assertEquals( 1, namespaces.size() );
assertEquals( ns, namespaces.get( 0 ) );
try {
metaStore.createNamespace( ns );
fail( "Exception expected when a namespace already exists and where we try to create it again" );
} catch ( MetaStoreNamespaceExistsException e ) {
// OK, we expected this.
}
metaStore.deleteNamespace( ns );
assertEquals( false, metaStore.namespaceExists( ns ) );
assertEquals( 0, metaStore.getNamespaces().size() );
}
@Test
public void testMetaStoreElementTypes() throws MetaStoreException {
IMetaStore metaStore = repository.getMetaStore();
assertNotNull( metaStore );
String ns = PentahoDefaults.NAMESPACE;
// We start with a clean slate...
//
assertEquals( 1, metaStore.getNamespaces().size() );
assertEquals( true, metaStore.namespaceExists( ns ) );
// Now create an element type
//
IMetaStoreElementType elementType = metaStore.newElementType( ns );
elementType.setName( PentahoDefaults.KETTLE_DATA_SERVICE_ELEMENT_TYPE_NAME );
elementType.setDescription( PentahoDefaults.KETTLE_DATA_SERVICE_ELEMENT_TYPE_DESCRIPTION );
metaStore.createElementType( ns, elementType );
IMetaStoreElementType verifyElementType = metaStore.getElementType( ns, elementType.getId() );
assertEquals( PentahoDefaults.KETTLE_DATA_SERVICE_ELEMENT_TYPE_NAME, verifyElementType.getName() );
assertEquals( PentahoDefaults.KETTLE_DATA_SERVICE_ELEMENT_TYPE_DESCRIPTION, verifyElementType.getDescription() );
verifyElementType = metaStore.getElementTypeByName( ns, PentahoDefaults.KETTLE_DATA_SERVICE_ELEMENT_TYPE_NAME );
assertEquals( PentahoDefaults.KETTLE_DATA_SERVICE_ELEMENT_TYPE_NAME, verifyElementType.getName() );
assertEquals( PentahoDefaults.KETTLE_DATA_SERVICE_ELEMENT_TYPE_DESCRIPTION, verifyElementType.getDescription() );
// Get the list of element type ids.
//
List<String> ids = metaStore.getElementTypeIds( ns );
assertNotNull( ids );
assertEquals( 1, ids.size() );
assertEquals( elementType.getId(), ids.get( 0 ) );
// Verify that we can't delete the namespace since it has content in it!
//
try {
metaStore.deleteNamespace( ns );
fail( "The namespace deletion didn't cause an exception because there are still an element type in it" );
} catch ( MetaStoreDependenciesExistsException e ) {
assertNotNull( e.getDependencies() );
assertEquals( 1, e.getDependencies().size() );
assertEquals( elementType.getId(), e.getDependencies().get( 0 ) );
}
metaStore.deleteElementType( ns, elementType );
assertEquals( 0, metaStore.getElementTypes( ns ).size() );
metaStore.deleteNamespace( ns );
}
@Test
public void testMetaStoreElements() throws MetaStoreException {
// Set up a namespace
//
String ns = PentahoDefaults.NAMESPACE;
IMetaStore metaStore = repository.getMetaStore();
if ( !metaStore.namespaceExists( ns ) ) {
metaStore.createNamespace( ns );
}
// And an element type
//
IMetaStoreElementType elementType = metaStore.newElementType( ns );
elementType.setName( PentahoDefaults.KETTLE_DATA_SERVICE_ELEMENT_TYPE_NAME );
elementType.setDescription( PentahoDefaults.KETTLE_DATA_SERVICE_ELEMENT_TYPE_DESCRIPTION );
metaStore.createElementType( ns, elementType );
// Now we play with elements...
//
IMetaStoreElement oneElement = populateElement( metaStore, elementType, "Element One" );
metaStore.createElement( ns, elementType, oneElement );
IMetaStoreElement verifyOneElement = metaStore.getElement( ns, elementType, oneElement.getId() );
assertNotNull( verifyOneElement );
validateElement( verifyOneElement, "Element One" );
assertEquals( 1, metaStore.getElements( ns, elementType ).size() );
IMetaStoreElement twoElement = populateElement( metaStore, elementType, "Element Two" );
metaStore.createElement( ns, elementType, twoElement );
IMetaStoreElement verifyTwoElement = metaStore.getElement( ns, elementType, twoElement.getId() );
assertNotNull( verifyTwoElement );
assertEquals( 2, metaStore.getElements( ns, elementType ).size() );
try {
metaStore.deleteElementType( ns, elementType );
fail( "Delete element type failed to properly detect element dependencies" );
} catch ( MetaStoreDependenciesExistsException e ) {
List<String> ids = e.getDependencies();
assertEquals( 2, ids.size() );
assertTrue( ids.contains( oneElement.getId() ) );
assertTrue( ids.contains( twoElement.getId() ) );
}
metaStore.deleteElement( ns, elementType, oneElement.getId() );
assertEquals( 1, metaStore.getElements( ns, elementType ).size() );
metaStore.deleteElement( ns, elementType, twoElement.getId() );
assertEquals( 0, metaStore.getElements( ns, elementType ).size() );
}
protected IMetaStoreElement populateElement( IMetaStore metaStore, IMetaStoreElementType elementType, String name )
throws MetaStoreException {
IMetaStoreElement element = metaStore.newElement();
element.setElementType( elementType );
element.setName( name );
for ( int i = 1; i <= 5; i++ ) {
element.addChild( metaStore.newAttribute( "id " + i, "value " + i ) );
}
IMetaStoreAttribute subAttr = metaStore.newAttribute( "sub-attr", null );
for ( int i = 101; i <= 110; i++ ) {
subAttr.addChild( metaStore.newAttribute( "sub-id " + i, "sub-value " + i ) );
}
element.addChild( subAttr );
return element;
}
protected void validateElement( IMetaStoreElement element, String name ) throws MetaStoreException {
assertEquals( name, element.getName() );
assertEquals( 6, element.getChildren().size() );
for ( int i = 1; i <= 5; i++ ) {
IMetaStoreAttribute child = element.getChild( "id " + i );
assertEquals( "value " + i, child.getValue() );
}
IMetaStoreAttribute subAttr = element.getChild( "sub-attr" );
assertNotNull( subAttr );
assertEquals( 10, subAttr.getChildren().size() );
for ( int i = 101; i <= 110; i++ ) {
IMetaStoreAttribute child = subAttr.getChild( "sub-id " + i );
assertNotNull( child );
assertEquals( "sub-value " + i, child.getValue() );
}
}
@Test
public void doesNotChangeFileWhenFailsToRename_slaves() throws Exception {
final SlaveServer server1 = new SlaveServer();
final SlaveServer server2 = new SlaveServer();
try {
testDoesNotChangeFileWhenFailsToRename( server1, server2, new Callable<RepositoryElementInterface>() {
@Override
public RepositoryElementInterface call() throws Exception {
return repository.loadSlaveServer( server2.getObjectId(), null );
}
} );
} finally {
repository.deleteSlave( server1.getObjectId() );
repository.deleteSlave( server2.getObjectId() );
}
}
@Test
public void doesNotChangeFileWhenFailsToRename_clusters() throws Exception {
final ClusterSchema schema1 = new ClusterSchema();
final ClusterSchema schema2 = new ClusterSchema();
try {
testDoesNotChangeFileWhenFailsToRename( schema1, schema2, new Callable<RepositoryElementInterface>() {
@Override
public RepositoryElementInterface call() throws Exception {
return repository.loadClusterSchema( schema2.getObjectId(), null, null );
}
} );
} finally {
repository.deleteClusterSchema( schema1.getObjectId() );
repository.deleteClusterSchema( schema2.getObjectId() );
}
}
@Test
public void doesNotChangeFileWhenFailsToRename_partitions() throws Exception {
final PartitionSchema schema1 = new PartitionSchema();
final PartitionSchema schema2 = new PartitionSchema();
try {
testDoesNotChangeFileWhenFailsToRename( schema1, schema2, new Callable<RepositoryElementInterface>() {
@Override
public RepositoryElementInterface call() throws Exception {
return repository.loadPartitionSchema( schema2.getObjectId(), null );
}
} );
} finally {
repository.deletePartitionSchema( schema1.getObjectId() );
repository.deletePartitionSchema( schema2.getObjectId() );
}
}
private void testDoesNotChangeFileWhenFailsToRename( RepositoryElementInterface element1,
RepositoryElementInterface element2, Callable<RepositoryElementInterface> loader ) throws Exception {
final String name1 = "name1";
final String name2 = "name2";
element1.setName( name1 );
element2.setName( name2 );
repository.save( element1, "", null );
repository.save( element2, "", null );
element2.setName( name1 );
try {
repository.save( element2, "", null, true );
fail( "A naming conflict should occur" );
} catch ( KettleException e ) {
// expected to be thrown
element2.setName( name2 );
}
RepositoryElementInterface loaded = loader.call();
assertEquals( element2, loaded );
}
@Test
public void testExportWithRules() throws Exception {
String fileName = "testExportWithRuled.xml";
final String exportFileName = new File( fileName ).getAbsolutePath(); //$NON-NLS-1$
RepositoryDirectoryInterface rootDir = initRepo();
String transWithoutNoteName = "2" + EXP_DBMETA_NAME;
TransMeta transWithoutNote = createTransMeta( transWithoutNoteName );
String transUniqueName = EXP_TRANS_NAME.concat( transWithoutNoteName );
RepositoryDirectoryInterface transDir = rootDir.findDirectory( DIR_TRANSFORMATIONS );
repository.save( transWithoutNote, VERSION_COMMENT_V1, null );
deleteStack.push( transWithoutNote ); // So this transformation is cleaned up afterward
assertNotNull( transWithoutNote.getObjectId() );
assertTrue( hasVersionWithComment( transWithoutNote, VERSION_COMMENT_V1 ) );
assertTrue( repository.exists( transUniqueName, transDir, RepositoryObjectType.TRANSFORMATION ) );
// Second transformation (contained note)
String transWithNoteName = "1" + EXP_DBMETA_NAME;
TransMeta transWithNote = createTransMeta( transWithNoteName );
transUniqueName = EXP_TRANS_NAME.concat( EXP_DBMETA_NAME );
TransMeta transWithRules = createTransMeta( EXP_DBMETA_NAME );
NotePadMeta note = new NotePadMeta( "Note Message", 1, 1, 100, 5 );
transWithRules.addNote( note );
repository.save( transWithRules, VERSION_COMMENT_V1, null );
deleteStack.push( transWithRules ); // So this transformation is cleaned up afterward
assertNotNull( transWithRules.getObjectId() );
assertTrue( hasVersionWithComment( transWithRules, VERSION_COMMENT_V1 ) );
assertTrue( repository.exists( transUniqueName, transDir, RepositoryObjectType.TRANSFORMATION ) );
// create rules for export to .xml file
List<ImportRuleInterface> rules = new AbstractList<ImportRuleInterface>() {
@Override
public ImportRuleInterface get( int index ) {
TransformationHasANoteImportRule rule = new TransformationHasANoteImportRule();
rule.setEnabled( true );
return rule;
}
@Override
public int size() {
return 1;
}
};
ImportRules importRules = new ImportRules();
importRules.setRules( rules );
// create exporter
IRepositoryExporter exporter = repository.getExporter();
exporter.setImportRulesToValidate( importRules );
// export itself
try {
exporter.exportAllObjects( new MockProgressMonitorListener(), exportFileName, null, "all" ); //$NON-NLS-1$
FileObject exportFile = KettleVFS.getFileObject( exportFileName );
assertNotNull( exportFile );
MockRepositoryExportParser parser = new MockRepositoryExportParser();
SAXParserFactory.newInstance().newSAXParser().parse( KettleVFS.getInputStream( exportFile ), parser );
if ( parser.getFatalError() != null ) {
throw parser.getFatalError();
}
// assumed transformation with note will be here and only it
assertEquals( "Incorrect number of transformations", 1, parser.getNodesWithName(
RepositoryObjectType.TRANSFORMATION.getTypeDescription() ).size() ); //$NON-NLS-1$ //$NON-NLS-2$
} finally {
KettleVFS.getFileObject( exportFileName ).delete();
}
}
@Test
public void testCreateRepositoryDirectory() throws KettleException {
RepositoryDirectoryInterface tree = repository.loadRepositoryDirectoryTree();
repository.createRepositoryDirectory( tree.findDirectory( "home" ), "/admin1" );
repository.createRepositoryDirectory( tree, "/home/admin2" );
repository.createRepositoryDirectory( tree, "/home/admin2/new1" );
RepositoryDirectoryInterface repositoryDirectory =
repository.createRepositoryDirectory( tree, "/home/admin2/new1" );
repository.getJobAndTransformationObjects( repositoryDirectory.getObjectId(), false );
}
@Test
public void testLoadJob() throws Exception {
RepositoryDirectoryInterface rootDir = initRepo();
JobMeta jobMeta = createJobMeta( EXP_JOB_NAME );
RepositoryDirectoryInterface jobsDir = rootDir.findDirectory( DIR_JOBS );
repository.save( jobMeta, VERSION_COMMENT_V1, null );
deleteStack.push( jobMeta );
JobMeta fetchedJob = repository.loadJob( EXP_JOB_NAME, jobsDir, null, null );
JobMeta jobMetaById = repository.loadJob( jobMeta.getObjectId(), null );
assertEquals( fetchedJob, jobMetaById );
assertNotNull( fetchedJob.getMetaStore() );
assertTrue( fetchedJob.getMetaStore() == jobMetaById.getMetaStore() );
}
protected static class LogListener implements KettleLoggingEventListener {
private List<KettleLoggingEvent> events = new ArrayList<>();
private LogLevel logThreshold;
public LogListener( LogLevel logThreshold ) {
this.logThreshold = logThreshold;
}
public List<KettleLoggingEvent> getEvents() {
return events;
}
public void eventAdded( KettleLoggingEvent event ) {
if ( logThreshold.getLevel() >= event.getLevel().getLevel() ) {
events.add( event );
}
}
}
}