/*!
* 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-2017 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.platform.plugin.services.importer;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.pentaho.metadata.repository.DomainAlreadyExistsException;
import org.pentaho.metadata.repository.DomainIdNullException;
import org.pentaho.metadata.repository.DomainStorageException;
import org.pentaho.platform.api.mimetype.IMimeType;
import org.pentaho.platform.api.repository2.unified.Converter;
import org.pentaho.platform.api.repository2.unified.IPlatformImportBundle;
import org.pentaho.platform.api.repository2.unified.IRepositoryDefaultAclHandler;
import org.pentaho.platform.api.repository2.unified.IRepositoryFileData;
import org.pentaho.platform.api.repository2.unified.IUnifiedRepository;
import org.pentaho.platform.api.repository2.unified.RepositoryFile;
import org.pentaho.platform.api.repository2.unified.RepositoryFileAcl;
import org.pentaho.platform.api.repository2.unified.RepositoryFileSid;
import org.pentaho.platform.plugin.services.importexport.ImportSession;
import org.pentaho.platform.plugin.services.importexport.exportManifest.ExportManifestFormatException;
import org.pentaho.platform.plugin.services.messages.Messages;
import org.pentaho.platform.repository.RepositoryFilenameUtils;
import org.springframework.util.Assert;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* User: nbaker Date: 5/29/12
*/
public class RepositoryFileImportFileHandler implements IPlatformImportHandler {
private static final Messages messages = Messages.getInstance();
private IUnifiedRepository repository;
private ThreadLocal<ImportSession> importSession = new ThreadLocal<ImportSession>();
private SolutionFileImportHelper solutionHelper = new SolutionFileImportHelper();
private HashMap<String, IMimeType> mimeTypeMap = new HashMap<String, IMimeType>();
private List<String> knownExtensions;
public RepositoryFileImportFileHandler( List<IMimeType> mimeTypes ) {
for ( IMimeType mimeType : mimeTypes ) {
this.mimeTypeMap.put( mimeType.getName(), mimeType );
}
}
IRepositoryDefaultAclHandler defaultAclHandler;
public Log getLogger() {
return getImportSession().getLogger();
}
public ImportSession getImportSession() {
return ImportSession.getSession();
}
@Override
public void importFile( IPlatformImportBundle bnd ) throws PlatformImportException {
if ( bnd instanceof RepositoryFileImportBundle == false ) {
throw new PlatformImportException( "Error importing bundle. RepositoryFileImportBundle expected" );
}
RepositoryFileImportBundle bundle = (RepositoryFileImportBundle) bnd;
if ( bundle.isSchedulable() == null ) {
bundle.setSchedulable( RepositoryFile.SCHEDULABLE_BY_DEFAULT );
}
String repositoryFilePath = RepositoryFilenameUtils.concat( bundle.getPath(), bundle.getName() );
getLogger().trace( "Processing [" + repositoryFilePath + "]" );
// Verify if destination already exists in the repository.
RepositoryFile file = repository.getFile( repositoryFilePath );
if ( file != null ) {
if ( file.isFolder() && getImportSession().getFoldersCreatedImplicitly().contains( repositoryFilePath ) ) {
getLogger().trace(
"Skipping entry for folder [" + repositoryFilePath + "]. It was already processed implicitly." );
} else {
if ( bundle.overwriteInRepossitory() ) {
// If file exists, overwrite is true and is not a folder then update it.
if ( !file.isFolder() ) {
file = finalAdjustFile( bundle, file );
copyFileToRepository( bundle, repositoryFilePath, file );
} else {
// The folder exists. Possible ACL changes.
getLogger().trace( "Existing folder [" + repositoryFilePath + "]" );
file = finalAdjustFolder( bundle, file.getId() );
repository.updateFolder( file, null );
if ( bundle.getAcl() != null ) {
updateAclFromBundle( false, bundle, file );
}
}
} else {
if ( getImportSession().getIsNotRunningImport() ) {
throw new PlatformImportException( messages.getString( "DefaultImportHandler.ERROR_0009_OVERWRITE_CONTENT",
repositoryFilePath ), PlatformImportException.PUBLISH_CONTENT_EXISTS_ERROR );
} else {
getLogger().trace( "Not importing existing file [" + repositoryFilePath + "]" );
ImportSession importSession = ImportSession.getSession();
importSession.getSkippedFiles().add( repositoryFilePath );
}
}
}
} else {
if ( bundle.isFolder() ) {
// The file doesn't exist and it is a folder. Create folder.
getLogger().trace( "Creating folder [" + repositoryFilePath + "]" );
final Serializable parentId = getParentId( repositoryFilePath );
bundle.setFile( bundle.getFile() );
RepositoryFile repoFile = finalAdjustFolder( bundle, null );
if ( bundle.getAcl() != null ) {
repoFile = repository.createFolder( parentId, repoFile, bundle.getAcl(), null );
updateAclFromBundle( true, bundle, repoFile );
} else {
repository.createFolder( parentId, repoFile, null );
}
} else {
// The file doesn't exist. Create file.
getLogger().trace( "Creating file [" + repositoryFilePath + "]" );
copyFileToRepository( bundle, repositoryFilePath, null );
}
}
}
private RepositoryFile finalAdjustFolder( RepositoryFileImportBundle bundle, Serializable id ) {
RepositoryFile.Builder builder = new RepositoryFile.Builder( bundle.getFile() ).hidden( bundle.isHidden() );
if ( id != null ) {
builder.id( id );
}
return builder.build();
}
private RepositoryFile finalAdjustFile( RepositoryFileImportBundle bundle, RepositoryFile file ) {
return new RepositoryFile.Builder( file ).hidden( isHiddenBundle( bundle ) ).schedulable( bundle.isSchedulable() )
.build();
}
private boolean isHiddenBundle( RepositoryFileImportBundle bundle ) {
if ( bundle.isHidden() != null ) {
return bundle.isHidden();
}
if ( solutionHelper.isInHiddenList( bundle.getName() ) ) {
return true;
}
return RepositoryFile.HIDDEN_BY_DEFAULT;
}
/**
* Copies the file bundle into the repository
*
* @param bundle
* @param repositoryPath
* @param file
*/
protected boolean copyFileToRepository( final RepositoryFileImportBundle bundle, final String repositoryPath,
final RepositoryFile file ) throws PlatformImportException {
// Compute the file extension
final String name = bundle.getName();
final String ext = RepositoryFilenameUtils.getExtension( name );
if ( StringUtils.isEmpty( ext ) ) {
getLogger().debug( "Skipping file without extension: " + name );
return false;
}
// Check the mime type
final String mimeType = bundle.getMimeType();
if ( mimeType == null ) {
getLogger().debug( "Skipping file without mime-type: " + name );
return false;
}
// Copy the file into the repository
try {
getLogger().trace( "copying file to repository: " + name );
if ( getMimeTypeMap().get( mimeType ) == null ) {
getLogger().debug( "Skipping file - mime type of " + mimeType + " is not registered :" + name );
}
Converter converter = getMimeTypeMap().get( mimeType ).getConverter();
if ( converter == null ) {
getLogger().debug( "Skipping file without converter: " + name );
return false;
}
RepositoryFile repositoryFile;
IRepositoryFileData data = converter.convert( bundle.getInputStream(), bundle.getCharset(), mimeType );
if ( null == file ) {
repositoryFile = createFile( bundle, repositoryPath, data );
if ( repositoryFile != null ) {
updateAclFromBundle( true, bundle, repositoryFile );
}
} else {
repositoryFile = updateFile( bundle, file, data );
updateAclFromBundle( false, bundle, repositoryFile );
}
converter.convertPostRepoSave( repositoryFile );
if ( repositoryFile != null ) {
getImportSession().addImportedRepositoryFile( repositoryFile );
}
return true;
} catch ( IOException e ) {
getLogger().warn( messages.getString( "DefaultImportHandler.WARN_0003_IOEXCEPTION", name ), e ); // TODO make sure
// string exists
return false;
}
}
/**
* Create a formal <code>RepositoryFileAcl</code> object for import.
*
* @param newFile
* Whether the file is being newly created or was pre-existing
* @param bundle
* The RepositoryImportBundle (which contains the effective manifest Acl)
* @param repositoryFile
* The <code>RepositoryFile</code> of the target file
*/
private void updateAclFromBundle( boolean newFile, RepositoryFileImportBundle bundle, RepositoryFile repositoryFile ) {
updateAcl( newFile, repositoryFile, bundle.getAcl() );
}
/**
* Create a formal <code>RepositoryFileAcl</code> object for import.
*
* @param newFile
* Whether the file is being newly created or was pre-existing
* @param repositoryFileAcl
* The effect Acl as defined in the manifest)
* @param repositoryFile
* The <code>RepositoryFile</code> of the target file
*/
private void updateAcl( boolean newFile, RepositoryFile repositoryFile, RepositoryFileAcl repositoryFileAcl ) {
getLogger().debug( "File " + ( newFile ? "is new" : "already exists" ) );
if ( repositoryFileAcl != null
&& ( getImportSession().isApplyAclSettings() || !getImportSession().isRetainOwnership() ) ) {
RepositoryFileAcl manifestAcl = repositoryFileAcl;
RepositoryFileAcl originalAcl = repository.getAcl( repositoryFile.getId() );
// Determine who will own this file
RepositoryFileSid newOwner;
if ( getImportSession().isRetainOwnership() ) {
if ( newFile ) {
getLogger().debug( "Getting Owner from Session" );
newOwner = getDefaultAcl( repositoryFile ).getOwner();
} else {
getLogger().debug( "Getting Owner from existing file" );
newOwner = originalAcl.getOwner();
}
} else {
getLogger().debug( "Getting Owner from Manifest" );
newOwner = manifestAcl.getOwner();
}
// Determine the Aces we will use for this file
RepositoryFileAcl useAclForPermissions; // The ACL we will use the permissions from
if ( getImportSession().isApplyAclSettings() && ( getImportSession().isOverwriteAclSettings() || newFile ) ) {
getLogger().debug( "Getting permissions from Manifest" );
useAclForPermissions = manifestAcl;
} else {
if ( newFile ) {
getLogger().debug( "Getting permissions from Default settings" );
useAclForPermissions = getDefaultAcl( repositoryFile );
} else {
getLogger().debug( "Getting permissions from existing file" );
useAclForPermissions = originalAcl;
}
}
// Make the new Acl if it has changed from the orignal
if ( !newOwner.equals( originalAcl.getOwner() ) || !useAclForPermissions.equals( originalAcl ) ) {
RepositoryFileAcl updatedAcl =
new RepositoryFileAcl( repositoryFile.getId(), newOwner, useAclForPermissions.isEntriesInheriting(),
useAclForPermissions.getAces() );
repository.updateAcl( updatedAcl );
}
}
}
private RepositoryFileAcl getDefaultAcl( RepositoryFile repositoryFile ) {
// ToDo: call default Acl creator when implemented. For now just return
// whatever is stored
// return repository.getAcl(repositoryFile.getId());
return defaultAclHandler.createDefaultAcl( repositoryFile.clone() );
}
/**
* Creates a new file in the repository
*
* @param bundle
* @param data
*/
protected RepositoryFile createFile( final RepositoryFileImportBundle bundle, final String repositoryPath,
final IRepositoryFileData data ) throws PlatformImportException {
if ( solutionHelper.isInApprovedExtensionList( repositoryPath ) ) {
final RepositoryFile file =
new RepositoryFile.Builder( bundle.getName() ).hidden( isHiddenBundle( bundle ) ).schedulable( bundle
.isSchedulable() ).title(
RepositoryFile.DEFAULT_LOCALE,
getTitle( bundle.getTitle() != null ? bundle.getTitle() : bundle.getName() ) ).versioned( true ).build();
final Serializable parentId = checkAndCreatePath( repositoryPath, getImportSession().getCurrentManifestKey() );
final RepositoryFileAcl acl = bundle.getAcl();
if ( null == acl ) {
return repository.createFile( parentId, file, data, bundle.getComment() );
} else {
return repository.createFile( parentId, file, data, acl, bundle.getComment() );
}
} else {
getLogger().trace(
"The file [" + repositoryPath
+ "] is not in the list of approved file extension that can be stored in the repository." );
return null;
}
}
/**
* Updates a file in the repository
*
*/
protected RepositoryFile updateFile( final RepositoryFileImportBundle bundle, final RepositoryFile file,
final IRepositoryFileData data ) throws PlatformImportException {
return repository.updateFile( file, data, bundle.getComment() );
}
/**
* Check path for existance. If path does not exist create folders as necessary to satisfy the path. When done return
* the Id of the path received.
*
* @param repositoryPath
* @return
*/
private Serializable checkAndCreatePath( String repositoryPath, String manifestKey ) throws PlatformImportException {
if ( getParentId( repositoryPath ) == null ) {
String parentPath = RepositoryFilenameUtils.getFullPathNoEndSeparator( repositoryPath );
String parentManifestKey = RepositoryFilenameUtils.getFullPathNoEndSeparator( manifestKey );
if ( !getImportSession().getFoldersCreatedImplicitly().contains( parentPath ) ) {
RepositoryFile parentFile = repository.getFile( parentPath );
if ( parentFile == null ) {
checkAndCreatePath( parentPath, parentManifestKey );
try {
parentFile = createFolderJustInTime( parentPath, parentManifestKey );
} catch ( Exception e ) {
throw new PlatformImportException( messages.getString(
"DefaultImportHandler.ERROR_0010_JUST_IN_TIME_FOLDER_CREATION", repositoryPath ) );
}
}
Serializable parentFileId = parentFile.getId();
Assert.notNull( parentFileId );
}
}
return getParentId( repositoryPath );
}
/**
* truncate the extension from the file name for the extension only if it is file with known extension
*
* @param name
* @return title
*/
protected String getTitle( String name ) {
if ( !StringUtils.isEmpty( name ) ) {
int dotIndex = name.lastIndexOf( '.' );
if ( dotIndex != -1 ) {
String extension = name.substring( dotIndex + 1 );
if ( knownExtensions != null && knownExtensions.contains( extension ) ) {
return name.substring( 0, dotIndex );
}
}
}
return name;
}
/**
* Returns the Id of the parent folder of the file path provided
*
* @param repositoryPath
* @return
*/
protected Serializable getParentId( final String repositoryPath ) {
Assert.notNull( repositoryPath );
final String parentPath = RepositoryFilenameUtils.getFullPathNoEndSeparator( repositoryPath );
final RepositoryFile parentFile = repository.getFile( parentPath );
if ( parentFile == null ) {
return null;
}
Serializable parentFileId = parentFile.getId();
Assert.notNull( parentFileId );
return parentFileId;
}
public IUnifiedRepository getRepository() {
return repository;
}
public void setRepository( IUnifiedRepository repository ) {
this.repository = repository;
}
public void setDefaultAclHandler( IRepositoryDefaultAclHandler defaultAclHandler ) {
this.defaultAclHandler = defaultAclHandler;
}
public RepositoryFile createFolderJustInTime( String folderPath, String manifestKey ) throws PlatformImportException,
DomainIdNullException, DomainAlreadyExistsException, DomainStorageException, IOException {
// The file doesn't exist and it is a folder. Create folder.
getLogger().trace( "Creating implied folder [" + folderPath + "]" );
final Serializable parentId = getParentId( folderPath );
Assert.notNull( parentId );
boolean isHidden;
if ( getImportSession().isFileHidden( manifestKey ) == null ) {
isHidden = false;
} else {
isHidden = getImportSession().isFileHidden( manifestKey );
}
RepositoryFile.Builder builder =
new RepositoryFile.Builder( RepositoryFilenameUtils.getName( folderPath ) ).path(
RepositoryFilenameUtils.getPath( folderPath ) ).folder( true ).hidden( isHidden );
RepositoryFile repoFile = builder.build();
RepositoryFileAcl repoAcl = getImportSession().processAclForFile( manifestKey );
if ( repoAcl != null ) {
repoFile = repository.createFolder( parentId, repoFile, repoAcl, null );
RepositoryFileAcl repositoryFileAcl = null;
try {
repositoryFileAcl =
getImportSession().getManifest().getExportManifestEntity( manifestKey ).getRepositoryFileAcl();
} catch ( NullPointerException e ) {
// If npe then manifest entry is not defined which is likely so just ignore
} catch ( ExportManifestFormatException e ) {
// Same goes here
}
updateAcl( true, repoFile, repositoryFileAcl );
} else {
repoFile = repository.createFolder( parentId, repoFile, null );
}
getImportSession().getFoldersCreatedImplicitly().add( folderPath );
return repoFile;
}
@Override
public List<IMimeType> getMimeTypes() {
return new ArrayList<IMimeType>( mimeTypeMap.values() );
}
public Map<String, IMimeType> getMimeTypeMap() {
return mimeTypeMap;
}
public void setKnownExtensions( List<String> knownExtensions ) {
this.knownExtensions = knownExtensions;
}
public List<String> getKnownExtensions() {
return knownExtensions;
}
}