/*!
* 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-2014 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.libraries.pensol;
import com.sun.jersey.core.header.FormDataContentDisposition;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.vfs.FileName;
import org.apache.commons.vfs.FileSystemException;
import org.pentaho.platform.repository2.unified.webservices.RepositoryFileDto;
import org.pentaho.platform.repository2.unified.webservices.RepositoryFileTreeDto;
import org.pentaho.platform.web.http.api.resources.DirectoryResource;
import org.pentaho.platform.web.http.api.resources.FileResource;
import org.pentaho.platform.web.http.api.resources.RepositoryImportResource;
import org.pentaho.reporting.libraries.base.util.FastStack;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
* @author Marco Vala
*/
public class JCRSolutionDirectFileModel implements SolutionFileModel {
private static final Log logger = LogFactory.getLog( JCRSolutionDirectFileModel.class );
private static final String SLASH = "/";
private static final String COLON = ":";
private static final String DO_GET_ROOT_CHILDREN = "doGetRootChildren";
private static final String DO_GET_FILE_OR_DIR_AS_DOWNLOAD = "doGetFileOrDirAsDownload";
private static final String FAILED_TO_ACCESS_REPOSITORY = "Failed to access repository";
private static final String FILE_NOT_FOUND = "File does not exist: {0}";
private static final String FAILED_TO_WRITE_FILE = "Failed to write file: {0}";
private static final String NULL_OBJECT = "Repository returned <null> for file: {0}";
private static final String NULL_MODIFIED_DATE = "Repository returned <null> for last-modified-date on file: ";
private static final String INVALID_USERNAME_OR_PASSWORD = "Invalid username or password";
private static final String BAD_RESPONSE = "Failed with error-code: {0}";
private static final String NOT_SUPPORTED = "Operation not supported: {0}";
private RepositoryFileTreeDto root;
private FileResource fileRes;
private DirectoryResource dirRes;
private RepositoryImportResource importRes;
private Method doGetRootChildrenMethod;
private Method doGetFileOrDirAsDownloadMethod;
public JCRSolutionDirectFileModel() {
this.fileRes = new FileResource();
this.dirRes = new DirectoryResource();
this.importRes = new RepositoryImportResource();
// check if [5.0-SNAPSHOT] backports are needed
try {
/*
[5.0-SNAPSHOT]
/api/repo/files/children?depth=-1&filter=*&showHidden=true
public RepositoryFileTreeDto doGetRootChildren(
@QueryParam("depth") Integer depth,
@QueryParam("filter") String filter,
@QueryParam("showHidden") Boolean showHidden )
*/
this.doGetRootChildrenMethod = this.fileRes.getClass().getDeclaredMethod( DO_GET_ROOT_CHILDREN,
Integer.class, String.class, Boolean.class );
} catch ( NoSuchMethodException e ) {
this.doGetRootChildrenMethod = null;
}
try {
/*
[5.0-SNAPSHOT]
/api/repo/files/{0}/download?withManifest=false
public Response doGetFileOrDirAsDownload(
@PathParam("pathId") String pathId,
@QueryParam("withManifest") String strWithManifest )
*/
this.doGetFileOrDirAsDownloadMethod = this.fileRes.getClass().getDeclaredMethod( DO_GET_FILE_OR_DIR_AS_DOWNLOAD,
String.class, String.class );
} catch ( NoSuchMethodException e ) {
this.doGetFileOrDirAsDownloadMethod = null;
}
}
////
//// Repository access
////
private RepositoryFileTreeDto getFileTree() throws FileSystemException {
/*
/api/repo/files/tree?depth=-1&filter=*&showHidden=true
public RepositoryFileTreeDto doGetRootTree(
@QueryParam( "depth" ) Integer depth,
@QueryParam( "filter" ) String filter,
@QueryParam( "showHidden" ) Boolean showHidden,
@DefaultValue( "false" ) @QueryParam( "includeAcls" ) Boolean includeAcls )
*/
final int depth = -1;
final String filter = "*";
final boolean showHidden = true;
RepositoryFileTreeDto fileTree = null;
if ( this.doGetRootChildrenMethod == null ) {
fileTree = this.fileRes.doGetRootTree( depth, filter, showHidden, false );
} else {
// apply [5.0-SNAPSHOT] backport
try {
fileTree =
(RepositoryFileTreeDto) this.doGetRootChildrenMethod.invoke( this.fileRes, depth, filter, showHidden );
} catch ( InvocationTargetException e ) {
throw new FileSystemException( FAILED_TO_ACCESS_REPOSITORY );
} catch ( IllegalAccessException e ) {
throw new FileSystemException( FAILED_TO_ACCESS_REPOSITORY );
}
}
return fileTree;
}
private RepositoryFileTreeDto getTreeNode( FileName fullName ) throws FileSystemException {
if ( this.root == null ) {
refresh();
}
return searchTreeNode( stackName( fullName ), this.root );
}
private RepositoryFileTreeDto searchTreeNode( FastStack<String> fullName, RepositoryFileTreeDto treeNode ) {
// no more name parts to search, found file in subtree
if ( fullName.size() == 0 ) {
return treeNode;
}
// search recursively for each name part in subtree
final String fileOrDirName = fullName.peek();
final List<RepositoryFileTreeDto> children = treeNode.getChildren();
for ( final RepositoryFileTreeDto child : children ) {
final String childName = child.getFile().getName();
if ( fileOrDirName.equals( childName ) ) {
fullName.pop();
return searchTreeNode( fullName, child );
}
}
// didn't find file
return null;
}
private FastStack<String> stackName( FileName fullName ) {
final FastStack<String> stack = new FastStack<String>();
while ( fullName != null ) {
final String name = fullName.getBaseName().trim();
if ( !name.equals( "" ) ) {
stack.push( name );
}
fullName = fullName.getParent();
}
return stack;
}
private RepositoryFileDto getFile( FileName fullName ) throws FileSystemException {
final RepositoryFileTreeDto tree = getTreeNode( fullName );
if ( tree == null ) {
throw new FileSystemException( FILE_NOT_FOUND, fullName );
}
return tree.getFile();
}
private List<RepositoryFileTreeDto> getChildNodes( final FileName fullName ) throws FileSystemException {
final RepositoryFileTreeDto tree = getTreeNode( fullName );
if ( tree == null ) {
throw new FileSystemException( FILE_NOT_FOUND, fullName );
}
final List<RepositoryFileTreeDto> children = tree.getChildren();
return children == null ? Collections.<RepositoryFileTreeDto>emptyList() : children;
}
private String pathToId( String path ) {
return path.replace( SLASH, COLON );
}
private void throwExceptionOnBadResponse( Response response ) throws FileSystemException {
final int status = response.getStatus();
switch( status ) {
case HttpStatus.SC_OK:
logger.debug( "OK" );
// response OK => do not throw exception and continue execution
break;
case HttpStatus.SC_UNAUTHORIZED:
case HttpStatus.SC_FORBIDDEN:
case HttpStatus.SC_MOVED_TEMPORARILY:
logger.debug( "FORBIDDEN" );
throw new FileSystemException( INVALID_USERNAME_OR_PASSWORD );
default:
logger.debug( "ERROR " + status );
throw new FileSystemException( BAD_RESPONSE, status );
}
}
////
//// SolutionFileModel implementation
////
@Override
public void refresh() throws FileSystemException {
logger.debug( "refresh" );
this.root = getFileTree();
}
@Override
public String[] getChilds( final FileName fullName ) throws FileSystemException {
logger.debug( "getChilds: " + fullName );
final List<RepositoryFileTreeDto> children = getChildNodes( fullName );
logger.debug( "size = " + children.size() );
final String[] childrenArray = new String[ children.size() ];
for ( int i = 0; i < children.size(); i++ ) {
final RepositoryFileTreeDto treeNode = children.get( i );
if ( treeNode != null ) {
final RepositoryFileDto file = treeNode.getFile();
if ( file != null ) {
logger.debug( "file " + file.getName() );
childrenArray[ i ] = file.getName();
} else {
throw new FileSystemException( NULL_OBJECT );
}
}
}
return childrenArray;
}
@Override
public boolean exists( final FileName fullName ) throws FileSystemException {
return ( getTreeNode( fullName ) != null );
}
@Override
public boolean isVisible( final FileName fullName ) throws FileSystemException {
return !getFile( fullName ).isHidden();
}
@Override
public boolean isDirectory( final FileName fullName ) throws FileSystemException {
return getFile( fullName ).isFolder();
}
@Override
public long getLastModifiedDate( final FileName fullName ) throws FileSystemException {
final Date lastModifiedDate = getFile( fullName ).getLastModifiedDate();
if ( lastModifiedDate == null ) {
logger.error( NULL_MODIFIED_DATE + fullName );
return -1;
}
return lastModifiedDate.getTime();
}
@Override
public String getLocalizedName( final FileName fullName ) throws FileSystemException {
return getFile( fullName ).getTitle();
}
@Override
public void setDescription( final FileName fullName, final String description ) throws FileSystemException {
getFile( fullName ).setDescription( description );
}
@Override
public String getDescription( final FileName fullName ) throws FileSystemException {
return getFile( fullName ).getDescription();
}
@Override
public long getContentSize( final FileName fullName ) throws FileSystemException {
return getFile( fullName ).getFileSize();
}
@Override
public byte[] getData( final FileName fullName ) throws FileSystemException {
/*
/api/repo/files/{0}/download?withManifest=false
public Response doGetFileOrDirAsDownloadMethod(
@PathParam("pathId") String pathId,
@QueryParam("withManifest") String strWithManifest )
*/
logger.debug( "getData: " + fullName );
try {
final String fileId = pathToId( fullName.getPath() );
Response response = null;
if ( this.doGetFileOrDirAsDownloadMethod == null ) {
response = this.fileRes.doGetFileOrDirAsDownload( null, fileId, "false" );
} else {
// apply [5.0-SNAPSHOT] backport
try {
response = (Response) this.doGetFileOrDirAsDownloadMethod.invoke( this.fileRes, fileId, "false" );
} catch ( InvocationTargetException e ) {
throw new FileSystemException( FAILED_TO_ACCESS_REPOSITORY );
} catch ( IllegalAccessException e ) {
throw new FileSystemException( FAILED_TO_ACCESS_REPOSITORY );
}
}
throwExceptionOnBadResponse( response );
StreamingOutput output = (StreamingOutput) response.getEntity();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
output.write( stream );
return stream.toByteArray();
} catch ( FileNotFoundException e ) {
throw new FileSystemException( FILE_NOT_FOUND, fullName );
} catch ( IOException e ) {
throw new FileSystemException( FAILED_TO_WRITE_FILE, fullName );
}
}
@Override
public void setData( final FileName fullName, final byte[] data ) throws FileSystemException {
/*
/api/repo/files/import
public Response doPostImport(
@FormDataParam("importDir") String uploadDir,
@FormDataParam("fileUpload") InputStream fileIS,
@FormDataParam("overwriteFile") String overwriteFile,
@FormDataParam("overwriteAclPermissions") String overwriteAclPermissions,
@FormDataParam("applyAclPermissions") String applyAclPermission,
@FormDataParam("retainOwnership") String retainOwnership,
@FormDataParam("charSet") String charSet,
@FormDataParam("logLevel") String logLevel,
@FormDataParam("fileUpload") FormDataContentDisposition fileInfo,
@FormDataParam("fileNameOverried) String fileNameOveride )
*/
logger.debug( "setData: " + fullName );
final String name = fullName.getBaseName();
final String parent = fullName.getParent().getPath();
final ByteArrayInputStream stream = new ByteArrayInputStream( data );
final FormDataContentDisposition fd = FormDataContentDisposition
.name( name )
.fileName( name )
.build();
Response response = this.importRes.doPostImport( parent, stream, "true", null, "true", "true", null, "WARN", fd, null );
throwExceptionOnBadResponse( response );
}
@Override
public void createFolder( final FileName fullName ) throws FileSystemException {
/*
/api/repo/dirs/{0}
public Response createDirs(
@PathParam( "pathId" ) String pathId )
*/
logger.debug( "createFolder: " + fullName );
Response response = this.dirRes.createDirs( pathToId( fullName.getPath() ) );
throwExceptionOnBadResponse( response );
refresh();
}
@Override
public boolean delete( final FileName fullName ) throws FileSystemException {
/*
/api/repo/files/delete
public Response doDeleteFiles(String params)
*/
logger.debug( "delete: " + fullName );
Response response = fileRes.doDeleteFiles( getFile( fullName ).getId() );
throwExceptionOnBadResponse( response );
refresh();
return true;
}
@Override public String getParamServiceUrl( FileName fullName ) throws FileSystemException {
throw new FileSystemException( NOT_SUPPORTED, "getParamServiceUrl" );
}
@Override public String getUrl( FileName fullName ) throws FileSystemException {
throw new FileSystemException( NOT_SUPPORTED, "getUrl" );
}
}