/*!
* 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.ui.repository.pur.repositoryexplorer.controller;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.extension.ExtensionPointHandler;
import org.pentaho.di.core.extension.KettleExtensionPoint;
import org.pentaho.di.core.logging.LogChannel;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.repository.ObjectId;
import org.pentaho.di.repository.Repository;
import org.pentaho.di.repository.RepositoryDirectory;
import org.pentaho.di.repository.RepositoryDirectoryInterface;
import org.pentaho.di.repository.RepositoryElementMetaInterface;
import org.pentaho.di.repository.RepositoryObjectType;
import org.pentaho.di.repository.pur.RepositoryObjectAccessException;
import org.pentaho.di.trans.TransMeta;
import org.pentaho.di.ui.repository.RepositoryExtension;
import org.pentaho.di.ui.repository.pur.repositoryexplorer.IUIEEUser;
import org.pentaho.di.ui.repository.pur.repositoryexplorer.model.UIEERepositoryDirectory;
import org.pentaho.di.ui.repository.pur.services.ITrashService;
import org.pentaho.di.ui.repository.pur.services.ITrashService.IDeletedObject;
import org.pentaho.di.ui.repository.repositoryexplorer.ControllerInitializationException;
import org.pentaho.di.ui.repository.repositoryexplorer.controllers.BrowseController;
import org.pentaho.di.ui.repository.repositoryexplorer.model.UIRepositoryDirectories;
import org.pentaho.di.ui.repository.repositoryexplorer.model.UIRepositoryDirectory;
import org.pentaho.di.ui.repository.repositoryexplorer.model.UIRepositoryObject;
import org.pentaho.di.ui.repository.repositoryexplorer.model.UIRepositoryObjects;
import org.pentaho.ui.xul.XulComponent;
import org.pentaho.ui.xul.XulEventSourceAdapter;
import org.pentaho.ui.xul.XulException;
import org.pentaho.ui.xul.binding.Binding;
import org.pentaho.ui.xul.binding.BindingConvertor;
import org.pentaho.ui.xul.components.XulButton;
import org.pentaho.ui.xul.components.XulConfirmBox;
import org.pentaho.ui.xul.components.XulPromptBox;
import org.pentaho.ui.xul.containers.XulDeck;
import org.pentaho.ui.xul.containers.XulTree;
import org.pentaho.ui.xul.util.XulDialogCallback;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.ResourceBundle;
public class TrashBrowseController extends BrowseController implements java.io.Serializable {
private static final long serialVersionUID = -3822571463115111325L; /* EESOURCE: UPDATE SERIALVERUID */
// ~ Static fields/initializers ======================================================================================
private static final Class<?> PKG = IUIEEUser.class;
// ~ Instance fields =================================================================================================
protected ResourceBundle messages = new ResourceBundle() {
@Override
public Enumeration<String> getKeys() {
return null;
}
@Override
protected Object handleGetObject( String key ) {
return BaseMessages.getString( PKG, key );
}
};
protected XulTree trashFileTable;
protected XulDeck deck;
protected List<UIDeletedObject> selectedTrashFileItems;
protected TrashDirectory trashDir = new TrashDirectory();
protected ITrashService trashService;
protected List<IDeletedObject> trash;
protected XulButton undeleteButton;
protected XulButton deleteButton;
// ~ Constructors ====================================================================================================
public TrashBrowseController() {
super();
}
// ~ Methods =========================================================================================================
/**
* Intercept the repositoryDirectory.children and add the Trash directory to the end.
*/
@Override
protected Binding createDirectoryBinding() {
bf.setBindingType( Binding.Type.ONE_WAY );
return bf.createBinding( this, "repositoryDirectory", folderTree, "elements", //$NON-NLS-1$//$NON-NLS-2$
new BindingConvertor<UIRepositoryDirectory, UIRepositoryDirectory>() {
@Override
public UIRepositoryDirectory sourceToTarget( final UIRepositoryDirectory value ) {
if ( value == null || value.size() == 0 ) {
return null;
}
if ( !value.get( value.size() - 1 ).equals( trashDir ) ) {
// add directly to children collection to bypass events
value.getChildren().add( trashDir );
}
return value;
}
@Override
public UIRepositoryDirectory targetToSource( final UIRepositoryDirectory value ) {
// not used
return value;
}
} );
}
protected class TrashDirectory extends UIEERepositoryDirectory {
private static final long serialVersionUID = 6184312253116517468L;
@Override
public String getImage() {
return "ui/images/trash_tree.svg"; //$NON-NLS-1$
}
@Override
public String getName() {
return BaseMessages.getString( PKG, "Trash" ); //$NON-NLS-1$
}
@Override
public UIRepositoryDirectories getChildren() {
return new UIRepositoryDirectories();
}
@Override
public UIRepositoryObjects getRepositoryObjects() throws KettleException {
return new UIRepositoryObjects();
}
}
@Override
public void init( Repository repository ) throws ControllerInitializationException {
super.init( repository );
try {
trashService = (ITrashService) repository.getService( ITrashService.class );
} catch ( Throwable e ) {
throw new ControllerInitializationException( e );
}
}
protected void doCreateBindings() {
deck = (XulDeck) document.getElementById( "browse-tab-right-panel-deck" ); //$NON-NLS-1$
trashFileTable = (XulTree) document.getElementById( "deleted-file-table" ); //$NON-NLS-1$
deleteButton = (XulButton) document.getElementById( "delete-button" ); //$NON-NLS-1$
undeleteButton = (XulButton) document.getElementById( "undelete-button" ); //$NON-NLS-1$
bf.setBindingType( Binding.Type.ONE_WAY );
BindingConvertor<List<UIDeletedObject>, Boolean>
buttonConverter =
new BindingConvertor<List<UIDeletedObject>, Boolean>() {
@Override public Boolean sourceToTarget( List<UIDeletedObject> value ) {
if ( value != null && value.size() > 0 ) {
return true;
}
return false;
}
@Override public List<UIDeletedObject> targetToSource( Boolean value ) {
return null;
}
};
bf.createBinding( trashFileTable, "selectedItems", this, "selectedTrashFileItems" ); //$NON-NLS-1$ //$NON-NLS-2$
bf.createBinding( trashFileTable, "selectedItems", deleteButton, "!disabled", buttonConverter ); //$NON-NLS-1$ //$NON-NLS-2$
bf.createBinding( trashFileTable, "selectedItems", undeleteButton, "!disabled", buttonConverter ); //$NON-NLS-1$ //$NON-NLS-2$
bf.createBinding( trashFileTable, "selectedItems", "trash-context-delete", "!disabled", buttonConverter ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
bf.createBinding( trashFileTable, "selectedItems", "trash-context-restore", "!disabled", buttonConverter ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
bf.setBindingType( Binding.Type.ONE_WAY );
bf.createBinding( this, "trash", trashFileTable, "elements", //$NON-NLS-1$ //$NON-NLS-2$
new BindingConvertor<List<IDeletedObject>, List<UIDeletedObject>>() {
@Override
public List<UIDeletedObject> sourceToTarget( List<IDeletedObject> trash ) {
List<UIDeletedObject> newList = new ArrayList<UIDeletedObject>( trash.size() );
for ( IDeletedObject obj : trash ) {
newList.add( new UIDeletedObject( obj ) );
}
Collections.sort( newList, new UIDeletedObjectComparator() );
return newList;
}
@Override
public List<IDeletedObject> targetToSource( List<UIDeletedObject> elements ) {
return null;
}
} );
}
/**
* An IDeletedObject that is also a XulEventSource.
*/
public static class UIDeletedObject extends XulEventSourceAdapter {
private IDeletedObject obj;
private static Comparator<UIDeletedObject> comparator = new UIDeletedObjectComparator();
public UIDeletedObject( final IDeletedObject obj ) {
this.obj = obj;
}
public String getOriginalParentPath() {
return obj.getOriginalParentPath();
}
public String getDeletedDate() {
Date date = obj.getDeletedDate();
String str = null;
if ( date != null ) {
SimpleDateFormat sdf = new SimpleDateFormat( "d MMM yyyy HH:mm:ss z" ); //$NON-NLS-1$
str = sdf.format( date );
}
return str;
}
public String getType() {
return obj.getType();
}
public ObjectId getId() {
return obj.getId();
}
public String getName() {
return obj.getName();
}
public String getImage() {
if ( RepositoryObjectType.TRANSFORMATION.name().equals( obj.getType() ) ) {
return "ui/images/transformation_tree.svg"; //$NON-NLS-1$
} else if ( RepositoryObjectType.JOB.name().equals( obj.getType() ) ) {
return "ui/images/job_tree.svg"; //$NON-NLS-1$
} else {
return "ui/images/folder.svg"; //$NON-NLS-1$
}
}
public Comparator<UIDeletedObject> getComparator() {
return comparator;
}
}
public static class UIDeletedObjectComparator implements Comparator<UIDeletedObject> {
public int compare( UIDeletedObject o1, UIDeletedObject o2 ) {
int cat1 = getValue( o1.getType() );
int cat2 = getValue( o2.getType() );
if ( cat1 != cat2 ) {
return cat1 - cat2;
}
String t1 = o1.getName();
String t2 = o2.getName();
if ( t1 == null ) {
t1 = ""; //$NON-NLS-1$
}
if ( t2 == null ) {
t2 = ""; //$NON-NLS-1$
}
return t1.compareToIgnoreCase( t2 );
}
private int getValue( final String type ) {
if ( type == null ) {
return 10;
} else {
return 20;
}
}
}
@Override
public void setSelectedFolderItems( List<UIRepositoryDirectory> selectedFolderItems ) {
if ( selectedFolderItems != null && selectedFolderItems.size() == 1
&& selectedFolderItems.get( 0 ).equals( trashDir ) ) {
try {
setTrash( trashService.getTrash() );
} catch ( KettleException e ) {
if ( mainController == null || !mainController.handleLostRepository( e ) ) {
throw new RuntimeException( e );
}
}
deck.setSelectedIndex( 1 );
} else {
deck.setSelectedIndex( 0 );
super.setSelectedFolderItems( selectedFolderItems );
}
}
public void setTrash( List<IDeletedObject> trash ) {
this.trash = trash;
firePropertyChange( "trash", null, trash ); //$NON-NLS-1$
}
public List<IDeletedObject> getTrash() {
return trash;
}
@Override
protected void moveFiles( List<UIRepositoryObject> objects, UIRepositoryDirectory targetDirectory ) throws Exception {
// If we're moving into the trash it's really a delete
if ( targetDirectory != trashDir ) {
super.moveFiles( objects, targetDirectory );
} else {
for ( UIRepositoryObject o : objects ) {
deleteContent( o );
}
}
}
public void delete() {
if ( selectedTrashFileItems != null && selectedTrashFileItems.size() > 0 ) {
List<ObjectId> ids = new ArrayList<ObjectId>();
for ( UIDeletedObject uiObj : selectedTrashFileItems ) {
ids.add( uiObj.getId() );
}
try {
trashService.delete( ids );
setTrash( trashService.getTrash() );
} catch ( Throwable th ) {
if ( mainController == null || !mainController.handleLostRepository( th ) ) {
displayExceptionMessage( BaseMessages.getString( PKG,
"TrashBrowseController.UnableToDeleteFile", th.getLocalizedMessage() ) ); //$NON-NLS-1$
}
}
} else {
// ui probably allowed the button to be enabled when it shouldn't have been enabled
throw new RuntimeException();
}
}
public void undelete() {
// make a copy because the selected trash items changes as soon as trashService.undelete is called
List<UIDeletedObject> selectedTrashFileItemsSnapshot = new ArrayList<UIDeletedObject>( selectedTrashFileItems );
if ( selectedTrashFileItemsSnapshot != null && selectedTrashFileItemsSnapshot.size() > 0 ) {
List<ObjectId> ids = new ArrayList<ObjectId>();
for ( UIDeletedObject uiObj : selectedTrashFileItemsSnapshot ) {
ids.add( uiObj.getId() );
}
try {
trashService.undelete( ids );
setTrash( trashService.getTrash() );
for ( UIDeletedObject uiObj : selectedTrashFileItemsSnapshot ) {
// find the closest UIRepositoryDirectory that is in the dirMap
RepositoryDirectoryInterface dir = repository.findDirectory( uiObj.getOriginalParentPath() );
while ( dir != null && dirMap.get( dir.getObjectId() ) == null ) {
dir = dir.getParent();
}
// now refresh that UIRepositoryDirectory so that the file/folders deck instantly refreshes on undelete
if ( dir != null ) {
dirMap.get( dir.getObjectId() ).refresh();
}
// if transformation or directory with transformations call extension to restore data services references.
if ( RepositoryObjectType.TRANSFORMATION.name().equals( uiObj.getType() ) ) {
TransMeta transMeta = repository.loadTransformation( uiObj.getId(), null );
ExtensionPointHandler
.callExtensionPoint( LogChannel.GENERAL, KettleExtensionPoint.TransAfterOpen.id, transMeta );
transMeta.clearChanged();
} else if ( !RepositoryObjectType.JOB.name().equals( uiObj.getType() ) ) {
// if not a transformation and not a job then is a Directory
RepositoryDirectoryInterface
actualDir =
repository.findDirectory(
uiObj.getOriginalParentPath() + RepositoryDirectory.DIRECTORY_SEPARATOR + uiObj.getName() );
if ( actualDir != null ) {
List<RepositoryElementMetaInterface> transformations = new ArrayList<>();
getAllTransformations( actualDir, transformations );
for ( RepositoryElementMetaInterface repositoryElementMetaInterface : transformations ) {
TransMeta transMeta = repository.loadTransformation( repositoryElementMetaInterface.getObjectId(), null );
ExtensionPointHandler
.callExtensionPoint( LogChannel.GENERAL, KettleExtensionPoint.TransAfterOpen.id, transMeta );
transMeta.clearChanged();
}
} else {
displayExceptionMessage( BaseMessages.getString( PKG, "TrashBrowseController.UnableToRestoreDirectory",
uiObj.getOriginalParentPath() + RepositoryDirectory.DIRECTORY_SEPARATOR + uiObj
.getName() ) ); //$NON-NLS-1$
}
}
}
deck.setSelectedIndex( 1 );
} catch ( Throwable th ) {
if ( mainController == null || !mainController.handleLostRepository( th ) ) {
displayExceptionMessage( BaseMessages.getString( PKG,
"TrashBrowseController.UnableToRestoreFile", th.getLocalizedMessage() ) ); //$NON-NLS-1$
}
}
} else {
// ui probably allowed the button to be enabled when it shouldn't have been enabled
throw new RuntimeException();
}
}
private static void getAllTransformations( RepositoryDirectoryInterface repositoryObject,
List<RepositoryElementMetaInterface> objectsTransformations ) throws KettleException {
//test if has sub-directories
if ( repositoryObject.getChildren() != null && repositoryObject.getChildren().size() > 0 ) {
for ( RepositoryDirectoryInterface subDirectory : repositoryObject.getChildren() ) {
getAllTransformations( subDirectory, objectsTransformations );
}
}
//getting all the transformations
repositoryObject.getRepositoryObjects().stream()
.filter( e -> RepositoryObjectType.TRANSFORMATION.equals( e.getObjectType() ) )
.forEach( objectsTransformations::add );
}
public void setSelectedTrashFileItems( List<UIDeletedObject> selectedTrashFileItems ) {
this.selectedTrashFileItems = selectedTrashFileItems;
}
@Override
protected void deleteFolder( UIRepositoryDirectory repoDir ) throws Exception {
deleteContent( repoDir );
}
@Override
protected void deleteContent( final UIRepositoryObject repoObject ) throws Exception {
try {
try {
ExtensionPointHandler.callExtensionPoint( LogChannel.GENERAL,
KettleExtensionPoint.AfterDeleteRepositoryObject.id, new RepositoryExtension( repoObject ) );
} catch ( Exception ex ) {
LogChannel.GENERAL.logError( "Error calling AfterDeleteRepositoryObject extension point", ex );
}
repoObject.delete();
} catch ( KettleException ke ) {
if ( repoDir != null ) {
repoDir.refresh();
}
if ( ke.getCause() instanceof RepositoryObjectAccessException ) {
moveDeletePrompt( ke, repoObject, new XulDialogCallback<Object>() {
public void onClose( XulComponent sender, Status returnCode, Object retVal ) {
if ( returnCode == Status.ACCEPT ) {
try {
( (UIEERepositoryDirectory) repoObject ).delete( true );
} catch ( Exception e ) {
if ( mainController == null || !mainController.handleLostRepository( e ) ) {
displayExceptionMessage( BaseMessages.getString( PKG, e.getLocalizedMessage() ) );
}
}
}
}
public void onError( XulComponent sender, Throwable t ) {
if ( mainController == null || !mainController.handleLostRepository( t ) ) {
throw new RuntimeException( t );
}
}
} );
} else {
if ( mainController == null || !mainController.handleLostRepository( ke ) ) {
throw ke;
}
}
}
if ( repoObject instanceof UIRepositoryDirectory ) {
directoryBinding.fireSourceChanged();
if ( repoDir != null ) {
repoDir.refresh();
}
}
selectedItemsBinding.fireSourceChanged();
}
@Override
protected void renameRepositoryObject( final UIRepositoryObject repoObject ) throws XulException {
// final Document doc = document;
XulPromptBox prompt = promptForName( repoObject );
prompt.addDialogCallback( new XulDialogCallback<String>() {
public void onClose( XulComponent component, Status status, String value ) {
if ( status == Status.ACCEPT ) {
final String newName = value;
try {
repoObject.setName( newName );
} catch ( KettleException ke ) {
if ( ke.getCause() instanceof RepositoryObjectAccessException ) {
moveDeletePrompt( ke, repoObject, new XulDialogCallback<Object>() {
public void onClose( XulComponent sender, Status returnCode, Object retVal ) {
if ( returnCode == Status.ACCEPT ) {
try {
( (UIEERepositoryDirectory) repoObject ).setName( newName, true );
} catch ( Exception e ) {
if ( mainController == null || !mainController.handleLostRepository( e ) ) {
displayExceptionMessage( BaseMessages.getString( PKG, e.getLocalizedMessage() ) );
}
}
}
}
public void onError( XulComponent sender, Throwable t ) {
if ( mainController == null || !mainController.handleLostRepository( t ) ) {
throw new RuntimeException( t );
}
}
} );
} else {
if ( mainController == null || !mainController.handleLostRepository( ke ) ) {
throw new RuntimeException( ke );
}
}
} catch ( Exception e ) {
if ( mainController == null || !mainController.handleLostRepository( e ) ) {
// convert to runtime exception so it bubbles up through the UI
throw new RuntimeException( e );
}
}
}
}
public void onError( XulComponent component, Throwable err ) {
if ( mainController == null || !mainController.handleLostRepository( err ) ) {
throw new RuntimeException( err );
}
}
} );
prompt.open();
}
protected boolean moveDeletePrompt( final KettleException ke, final UIRepositoryObject repoObject,
final XulDialogCallback<Object> action ) {
if ( ke.getCause() instanceof RepositoryObjectAccessException
&& ( (RepositoryObjectAccessException) ke.getCause() ).getObjectAccessType().equals(
RepositoryObjectAccessException.AccessExceptionType.USER_HOME_DIR )
&& repoObject instanceof UIEERepositoryDirectory ) {
try {
confirmBox = (XulConfirmBox) document.createElement( "confirmbox" ); //$NON-NLS-1$
confirmBox.setTitle( BaseMessages.getString( PKG, "TrashBrowseController.DeleteHomeFolderWarningTitle" ) ); //$NON-NLS-1$
confirmBox.setMessage( BaseMessages.getString( PKG, "TrashBrowseController.DeleteHomeFolderWarningMessage" ) ); //$NON-NLS-1$
confirmBox.setAcceptLabel( BaseMessages.getString( PKG, "Dialog.Ok" ) ); //$NON-NLS-1$
confirmBox.setCancelLabel( BaseMessages.getString( PKG, "Dialog.Cancel" ) ); //$NON-NLS-1$
confirmBox.addDialogCallback( new XulDialogCallback<Object>() {
public void onClose( XulComponent sender, Status returnCode, Object retVal ) {
if ( returnCode == Status.ACCEPT ) {
action.onClose( sender, returnCode, retVal );
}
}
public void onError( XulComponent sender, Throwable t ) {
if ( mainController == null || !mainController.handleLostRepository( t ) ) {
throw new RuntimeException( t );
}
}
} );
confirmBox.open();
} catch ( Exception e ) {
if ( mainController == null || !mainController.handleLostRepository( e ) ) {
throw new RuntimeException( e );
}
}
}
return false;
}
protected void displayExceptionMessage( String msg ) {
messageBox.setTitle( BaseMessages.getString( PKG, "Dialog.Error" ) ); //$NON-NLS-1$
messageBox.setAcceptLabel( BaseMessages.getString( PKG, "Dialog.Ok" ) ); //$NON-NLS-1$
messageBox.setMessage( msg );
messageBox.open();
}
}