/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2017 by Pentaho : http://www.pentaho.com
*
*******************************************************************************
*
* 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.ui.repo;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang.ClassUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
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.LogChannelInterface;
import org.pentaho.di.core.plugins.PluginInterface;
import org.pentaho.di.core.plugins.PluginRegistry;
import org.pentaho.di.core.plugins.RepositoryPluginType;
import org.pentaho.di.core.util.ExecutorUtil;
import org.pentaho.di.repository.AbstractRepository;
import org.pentaho.di.repository.ReconnectableRepository;
import org.pentaho.di.repository.RepositoriesMeta;
import org.pentaho.di.repository.Repository;
import org.pentaho.di.repository.RepositoryMeta;
import org.pentaho.di.trans.Trans;
import org.pentaho.di.ui.core.PropsUI;
import org.pentaho.di.ui.repo.model.RepositoryModel;
import org.pentaho.di.ui.spoon.Spoon;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.function.Supplier;
/**
* Created by bmorrise on 4/18/16.
*/
public class RepositoryConnectController {
public static final String DISPLAY_NAME = "displayName";
public static final String DESCRIPTION = "description";
public static final String IS_DEFAULT = "isDefault";
public static final String URL = "url";
public static final String DATABASE_CONNECTION = "databaseConnection";
public static final String SHOW_HIDDEN_FOLDERS = "showHiddenFolders";
public static final String LOCATION = "location";
public static final String DO_NOT_MODIFY = "doNotModify";
public static final String DEFAULT_URL = "defaultUrl";
public static final String ERROR_401 = "401";
private static Class<?> PKG = RepositoryConnectController.class;
private static LogChannelInterface log =
KettleLogStore.getLogChannelInterfaceFactory().create( RepositoryConnectController.class );
private RepositoryMeta currentRepository;
private RepositoryMeta connectedRepository;
private RepositoriesMeta repositoriesMeta;
private PluginRegistry pluginRegistry;
private Supplier<Spoon> spoonSupplier;
private List<RepositoryContollerListener> listeners = new ArrayList<>();
private boolean relogin = false;
public RepositoryConnectController( PluginRegistry pluginRegistry, Supplier<Spoon> spoonSupplier,
RepositoriesMeta repositoriesMeta ) {
this.pluginRegistry = pluginRegistry;
this.spoonSupplier = spoonSupplier;
this.repositoriesMeta = repositoriesMeta;
try {
repositoriesMeta.readData();
} catch ( KettleException ke ) {
log.logError( "Unable to load repositories", ke );
}
}
public RepositoryConnectController() {
this( PluginRegistry.getInstance(), Spoon::getInstance, new RepositoriesMeta() );
}
@SuppressWarnings( "unchecked" )
public String getPlugins() {
List<PluginInterface> plugins = pluginRegistry.getPlugins( RepositoryPluginType.class );
JSONArray list = new JSONArray();
for ( PluginInterface pluginInterface : plugins ) {
if ( !pluginInterface.getIds()[0].equals( "PentahoEnterpriseRepository" ) ) {
JSONObject repoJSON = new JSONObject();
repoJSON.put( "id", pluginInterface.getIds()[ 0 ] );
repoJSON.put( "name", pluginInterface.getName() );
repoJSON.put( "description", pluginInterface.getDescription() );
list.add( repoJSON );
}
}
return list.toString();
}
public boolean createRepository( String id, Map<String, Object> items ) {
try {
RepositoryMeta repositoryMeta = pluginRegistry.loadClass( RepositoryPluginType.class, id, RepositoryMeta.class );
repositoryMeta.populate( items, repositoriesMeta );
if ( repositoryMeta.getName() != null ) {
Repository repository =
pluginRegistry.loadClass( RepositoryPluginType.class, repositoryMeta.getId(), Repository.class );
repository.init( repositoryMeta );
if ( currentRepository != null ) {
if ( isCompatibleRepositoryEdit( repositoryMeta ) ) {
setConnectedRepository( repositoryMeta );
}
repositoriesMeta.removeRepository( repositoriesMeta.indexOfRepository( currentRepository ) );
}
repositoriesMeta.addRepository( repositoryMeta );
repositoriesMeta.writeData();
currentRepository = repositoryMeta;
if ( !testRepository( repository ) ) {
return false;
}
( (AbstractRepository) repository ).create();
}
} catch ( KettleException ke ) {
log.logError( "Unable to load repository type", ke );
return false;
}
return true;
}
private boolean isCompatibleRepositoryEdit( RepositoryMeta repositoryMeta ) {
if ( repositoriesMeta.indexOfRepository( currentRepository ) >= 0
&& connectedRepository != null
&& repositoryEquals( connectedRepository, currentRepository ) ) {
// only name / description / default changed ?
RepositoryMeta clone = repositoryMeta.clone();
clone.setName( connectedRepository.getName() );
clone.setDescription( connectedRepository.getDescription() );
clone.setDefault( connectedRepository.isDefault() );
return repositoryEquals( connectedRepository, clone );
}
return false;
}
private boolean repositoryEquals( RepositoryMeta repo1, RepositoryMeta repo2 ) {
return repo1.toJSONObject().equals( repo2.toJSONObject() );
}
@SuppressWarnings( "unchecked" )
public String getRepositories() {
JSONArray list = new JSONArray();
if ( repositoriesMeta != null ) {
for ( int i = 0; i < repositoriesMeta.nrRepositories(); i++ ) {
list.add( repositoriesMeta.getRepository( i ).toJSONObject() );
}
}
return list.toString();
}
public String getRepository( String name ) {
RepositoryMeta repositoryMeta = repositoriesMeta.findRepository( name );
if ( repositoryMeta != null ) {
currentRepository = repositoryMeta;
return repositoryMeta.toJSONObject().toString();
}
return "";
}
public DatabaseMeta getDatabase( String name ) {
return repositoriesMeta.searchDatabase( name );
}
public void removeDatabase( String name ) {
int index = repositoriesMeta.indexOfDatabase( repositoriesMeta.searchDatabase( name ) );
if ( index != -1 ) {
repositoriesMeta.removeDatabase( index );
}
save();
}
@SuppressWarnings( "unchecked" )
public String getDatabases() {
JSONArray list = new JSONArray();
for ( int i = 0; i < repositoriesMeta.nrDatabases(); i++ ) {
JSONObject databaseJSON = new JSONObject();
databaseJSON.put( "name", repositoriesMeta.getDatabase( i ).getName() );
list.add( databaseJSON );
}
return list.toString();
}
public void connectToRepository() throws KettleException {
connectToRepository( currentRepository );
}
public void connectToRepository( String username, String password ) throws KettleException {
connectToRepository( currentRepository, username, password );
}
public void connectToRepository( RepositoryMeta repositoryMeta ) throws KettleException {
connectToRepository( repositoryMeta, null, null );
}
public void connectToRepository( RepositoryMeta repositoryMeta, String username, String password ) throws KettleException {
final Repository repository = loadRepositoryObject( repositoryMeta.getId() );
repository.init( repositoryMeta );
repositoryConnect( repository, username, password );
if ( username != null ) {
getPropsUI().setLastRepositoryLogin( username );
}
Spoon spoon = spoonSupplier.get();
Runnable execute = () -> {
if ( spoon.getRepository() != null ) {
spoon.closeRepository();
} else {
spoon.closeAllJobsAndTransformations( true );
}
spoon.setRepository( repository );
setConnectedRepository( repositoryMeta );
fireListeners();
};
if ( spoon.getShell() != null ) {
spoon.getShell().getDisplay().asyncExec( execute );
} else {
execute.run();
}
}
private Repository loadRepositoryObject( String id ) throws KettleException {
Repository repository =
pluginRegistry.loadClass( RepositoryPluginType.class, id, Repository.class );
if ( repository instanceof ReconnectableRepository ) {
repository = wrapWithRepositoryTimeoutHandler( (ReconnectableRepository) repository );
}
return repository;
}
public void reconnectToRepository( String username, String password ) throws KettleException {
Repository currentRepositoryInstance = getConnectedRepositoryInstance();
reconnectToRepository( currentRepository, (ReconnectableRepository) currentRepositoryInstance, username, password );
}
private void reconnectToRepository( RepositoryMeta repositoryMeta, ReconnectableRepository repository,
String username, String password ) throws KettleException {
if ( username != null ) {
getPropsUI().setLastRepositoryLogin( username );
}
if ( repository.isConnected() ) {
repository.disconnect();
}
repository.init( repositoryMeta );
repositoryConnect( repository, username, password );
}
private void repositoryConnect( Repository repository, String username, String password ) throws KettleException {
ExecutorService executorService = ExecutorUtil.getExecutor();
Future<KettleException> future = executorService.submit( () -> {
ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader( Trans.class.getClassLoader() );
repository.connect( username, password );
} catch ( KettleException e ) {
return e;
} finally {
Thread.currentThread().setContextClassLoader( currentClassLoader );
}
return null;
} );
try {
KettleException exception = future.get();
if ( exception != null ) {
throw exception;
}
} catch ( InterruptedException | ExecutionException e ) {
throw new KettleException();
}
}
private boolean testRepository( Repository repository ) {
ExecutorService executorService = ExecutorUtil.getExecutor();
Future<Boolean> future = executorService.submit( () -> {
ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader( Trans.class.getClassLoader() );
return ( (AbstractRepository) repository ).test();
} finally {
Thread.currentThread().setContextClassLoader( currentClassLoader );
}
} );
try {
return future.get();
} catch ( InterruptedException | ExecutionException e ) {
return false;
}
}
public boolean deleteRepository( String name ) {
RepositoryMeta repositoryMeta = repositoriesMeta.findRepository( name );
int index = repositoriesMeta.indexOfRepository( repositoryMeta );
if ( index != -1 ) {
Spoon spoon = spoonSupplier.get();
if ( spoon.getRepositoryName() != null && spoon.getRepositoryName().equals( repositoryMeta.getName() ) ) {
spoon.closeRepository();
setConnectedRepository( null );
}
repositoriesMeta.removeRepository( index );
save();
}
return true;
}
public void addDatabase( DatabaseMeta databaseMeta ) {
if ( databaseMeta != null ) {
repositoriesMeta.addDatabase( databaseMeta );
save();
}
}
public boolean setDefaultRepository( String name ) {
RepositoryMeta repositoryMeta = repositoriesMeta.findRepository( name );
for ( int i = 0; i < repositoriesMeta.nrRepositories(); i++ ) {
repositoriesMeta.getRepository( i ).setDefault( false );
}
if ( repositoryMeta != null ) {
repositoryMeta.setDefault( true );
}
try {
repositoriesMeta.writeData();
} catch ( KettleException ke ) {
log.logError( "Unable to set default repository", ke );
}
return true;
}
public String getDefaultUrl() {
ResourceBundle resourceBundle = PropertyResourceBundle.getBundle( PKG.getPackage().getName() + ".plugin" );
return resourceBundle.getString( DEFAULT_URL );
}
public String getCurrentUser() {
return getPropsUI().getLastRepositoryLogin();
}
public void setCurrentRepository( RepositoryMeta repositoryMeta ) {
this.currentRepository = repositoryMeta;
}
public RepositoryMeta getCurrentRepository() {
return this.currentRepository;
}
public RepositoryMeta getConnectedRepository() {
return connectedRepository;
}
public void setConnectedRepository( RepositoryMeta connectedRepository ) {
this.connectedRepository = connectedRepository;
}
public RepositoryMeta getDefaultRepositoryMeta() {
for ( int i = 0; i < repositoriesMeta.nrRepositories(); i++ ) {
RepositoryMeta repositoryMeta = repositoriesMeta.getRepository( i );
if ( repositoryMeta.isDefault() ) {
return repositoryMeta;
}
}
return null;
}
public RepositoryMeta getRepositoryMetaByName( String name ) {
return repositoriesMeta.findRepository( name );
}
public boolean isConnected( String name ) {
if ( spoonSupplier.get().rep != null ) {
return spoonSupplier.get().rep.getName().equals( name );
}
return false;
}
public boolean isConnected() {
return spoonSupplier.get().rep != null;
}
public Repository getConnectedRepositoryInstance() {
return spoonSupplier.get().rep;
}
public void save() {
try {
repositoriesMeta.writeData();
} catch ( KettleException ke ) {
log.logError( "Unable to write to repositories", ke );
}
}
@SuppressWarnings( "unchecked" )
private Repository wrapWithRepositoryTimeoutHandler( ReconnectableRepository repository ) {
List<Class<?>> repositoryIntrerfaces = ClassUtils.getAllInterfaces( repository.getClass() );
Class<?>[] repositoryIntrerfacesArray = repositoryIntrerfaces.toArray( new Class<?>[repositoryIntrerfaces.size()] );
return (Repository) Proxy.newProxyInstance( repository.getClass().getClassLoader(), repositoryIntrerfacesArray,
new RepositorySessionTimeoutHandler( repository, this ) );
}
public PropsUI getPropsUI() {
return PropsUI.getInstance();
}
public void addListener( RepositoryContollerListener listener ) {
listeners.add( listener );
}
public void fireListeners() {
for ( RepositoryContollerListener listener : listeners ) {
listener.update();
}
}
public interface RepositoryContollerListener {
void update();
}
public boolean isRelogin() {
return relogin;
}
public void setRelogin( boolean relogin ) {
this.relogin = relogin;
}
public Map<String, Object> modelToMap( RepositoryModel model ) {
Map<String, Object> properties = new HashMap<>();
properties.put( DISPLAY_NAME, model.getDisplayName() );
properties.put( DESCRIPTION, model.getDescription() );
properties.put( IS_DEFAULT, model.getIsDefault() );
properties.put( URL, model.getUrl() );
properties.put( DATABASE_CONNECTION, model.getDatabaseConnection() );
properties.put( SHOW_HIDDEN_FOLDERS, model.getShowHiddenFolders() );
properties.put( LOCATION, model.getLocation() );
properties.put( DO_NOT_MODIFY, model.getDoNotModify() );
return properties;
}
@VisibleForTesting
boolean isDatabaseWithNameExist( DatabaseMeta databaseMeta, boolean isNew ) {
for ( int i = 0; i < repositoriesMeta.nrDatabases(); i++ ) {
final DatabaseMeta iterDatabase = repositoriesMeta.getDatabase( i );
if ( iterDatabase.getName().trim().equalsIgnoreCase( databaseMeta.getName().trim() ) ) {
if ( isNew || databaseMeta != iterDatabase ) { // do not check the same instance
return true;
}
}
}
return false;
}
}