/*! * 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-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.reporting.libraries.pensol; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.config.ClientConfig; import com.sun.jersey.api.client.config.DefaultClientConfig; import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.commons.vfs2.FileName; import org.apache.commons.vfs2.FileSystemException; import org.pentaho.platform.repository2.unified.webservices.RepositoryFileDto; import org.pentaho.platform.repository2.unified.webservices.RepositoryFileTreeDto; import org.pentaho.platform.util.RepositoryPathEncoder; import org.pentaho.reporting.libraries.base.util.FastStack; import org.pentaho.reporting.libraries.base.util.URLEncoder; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; public class JCRSolutionFileModel implements SolutionFileModel { private static final Log logger = LogFactory.getLog( JCRSolutionFileModel.class ); private static final String LOAD_REPOSITORY_SERVICE = LibPensolBoot.getInstance().getGlobalConfig().getConfigProperty( "org.pentaho.reporting.libraries.pensol.jcr.LoadRepositoryDoc" ); private static final String PARTIAL_LOADING_ENABLED = LibPensolBoot.getInstance().getGlobalConfig().getConfigProperty( "org.pentaho.reporting.libraries.pensol.jcr.PartialLoading", Boolean.TRUE.toString() ); private static final String LOAD_REPOSITORY_PARTIALLY_SERVICE = LibPensolBoot.getInstance().getGlobalConfig().getConfigProperty( "org.pentaho.reporting.libraries.pensol.jcr.LoadRepositoryDocPartially" ); private static final String CREATE_FOLDER_SERVICE = LibPensolBoot.getInstance().getGlobalConfig().getConfigProperty( "org.pentaho.reporting.libraries.pensol.jcr.CreateNewFolder" ); private static final String DOWNLOAD_SERVICE = LibPensolBoot.getInstance().getGlobalConfig().getConfigProperty( "org.pentaho.reporting.libraries.pensol.jcr.DownloadService" ); private static final String UPLOAD_SERVICE = LibPensolBoot.getInstance().getGlobalConfig().getConfigProperty( "org.pentaho.reporting.libraries.pensol.jcr.UploadService" ); public static final String RETRIEVE_CONTENT_SERVICE = LibPensolBoot.getInstance().getGlobalConfig().getConfigProperty( "org.pentaho.reporting.libraries.pensol.jcr.RetrieveContent" ); public static final String RETRIEVE_PARAMETER_URL_SERVICE = LibPensolBoot.getInstance().getGlobalConfig().getConfigProperty( "org.pentaho.reporting.libraries.pensol.jcr.RetrieveParameters" ); public static final String DELETE_FILE_OR_FOLDER = LibPensolBoot.getInstance().getGlobalConfig().getConfigProperty( "org.pentaho.reporting.libraries.pensol.jcr.delete.file.or.folder" ); private static final String BI_SERVER_NULL_OBJECT = "BI-Server returned a RepositoryFileTreeDto without an attached RepositoryFileDto. " + "Please file a bug report at http://jira.pentaho.org/browse/BISERVER !"; private static final String FILE_NOT_FOUND = "The specified file name does not exist: {0}"; //this is required to retrieve a prpt - if true we get z ZIP file with .locale info private static final String SLASH = "/"; // ":" --> "%3A" private static final String ENCODED_COLON = RepositoryPathEncoder.encodeURIComponent( ":" ); private Client client; private String url; private RepositoryFileTreeDto root; private HashMap<FileName, String> descriptionEntries; private long refreshTime; private String majorVersion; private String minorVersion; private String releaseVersion; private String buildVersion; private String milestoneVersion; private final boolean loadTreePartially; public JCRSolutionFileModel( final String url, final String username, final String password, final int timeout ) { if ( url == null ) { throw new NullPointerException(); } this.url = url; descriptionEntries = new HashMap<FileName, String>(); final ClientConfig config = new DefaultClientConfig(); config.getProperties().put( ClientConfig.PROPERTY_FOLLOW_REDIRECTS, true ); config.getProperties().put( ClientConfig.PROPERTY_READ_TIMEOUT, timeout ); this.client = Client.create( config ); this.client.addFilter( new CookiesHandlerFilter() ); // must be inserted before HTTPBasicAuthFilter this.client.addFilter( new HTTPBasicAuthFilter( username, password ) ); this.majorVersion = "999"; this.minorVersion = "999"; this.releaseVersion = "999"; this.buildVersion = "999"; this.milestoneVersion = "999"; this.loadTreePartially = Boolean.parseBoolean( PARTIAL_LOADING_ENABLED ); } // package-local constructor for testing purposes JCRSolutionFileModel( String url, Client client, boolean loadTreePartially ) { this.url = url; this.descriptionEntries = new HashMap<FileName, String>(); this.client = client; this.loadTreePartially = loadTreePartially; } public void refresh() throws IOException { RepositoryFileTreeDto tree; if ( loadTreePartially ) { WebResource resource = client.resource( url + LOAD_REPOSITORY_PARTIALLY_SERVICE ); tree = resource.path( "" ).accept( MediaType.APPLICATION_XML_TYPE ).get( RepositoryFileTreeDto.class ); tree = proxyRoot( tree ); } else { WebResource resource = client.resource( url + LOAD_REPOSITORY_SERVICE ); tree = resource.path( "" ).accept( MediaType.APPLICATION_XML_TYPE ).get( RepositoryFileTreeDto.class ); } setRoot( tree ); } private RepositoryFileTreeDtoProxy proxyRoot( RepositoryFileTreeDto original ) { // save it, as proxy will replace the list with empty List<RepositoryFileTreeDto> originalChildren = original.getChildren(); RepositoryFileTreeDtoProxy proxy = new RepositoryFileTreeDtoProxy( original, client, url ); if ( originalChildren == null ) { proxy.setChildren( Collections.<RepositoryFileTreeDto>emptyList() ); } else { // pre-populating root's direct children List<RepositoryFileTreeDto> proxiedChildren = new ArrayList<RepositoryFileTreeDto>( originalChildren.size() ); for ( RepositoryFileTreeDto child : originalChildren ) { proxiedChildren.add( new RepositoryFileTreeDtoProxy( child, client, url ) ); } proxy.setChildren( proxiedChildren ); } return proxy; } public void createFolder( final FileName file ) throws FileSystemException { try { final String path = URLEncoder .encodeUTF8( URLDecoder.decode( normalizePath( file.getPath().replaceAll( "\\+", "%2B" ) ), "UTF-8" ) ) .replaceAll( "\\!", "%21" ); final String service = MessageFormat.format( CREATE_FOLDER_SERVICE, path ); final WebResource resource = client.resource( url + service ); final ClientResponse response = resource.type( "text/plain" ).put( ClientResponse.class, null ); if ( response.getStatus() == 200 ) { refresh(); } else { throw new FileSystemException( "Failed with error-code", response.getStatus() ); } } catch ( FileSystemException fse ) { throw fse; } catch ( Exception e ) { throw new FileSystemException( "Failed", e ); } } private static String normalizePath( final String path ) { return RepositoryPathEncoder.encodeRepositoryPath( path ); } static String encodePathForRequest( String path ) { String pathEncoded = RepositoryPathEncoder.encodeRepositoryPath( path ); String utf8 = RepositoryPathEncoder.encodeURIComponent( pathEncoded ); // replace encoded colons with original return utf8.replaceAll( ENCODED_COLON, ":" ); } public RepositoryFileTreeDto getRoot() throws IOException { if ( root == null && refreshTime == 0 ) { refresh(); } return root; } public void setRoot( final RepositoryFileTreeDto root ) { if ( root == null ) { throw new NullPointerException(); } this.descriptionEntries.clear(); this.refreshTime = System.currentTimeMillis(); this.root = root; } public boolean isDirectory( final FileName file ) throws FileSystemException { final String[] fileName = computeFileNames( file ); final RepositoryFileTreeDto fileInfo = lookupNode( fileName ); if ( fileInfo == null ) { throw new FileSystemException( FILE_NOT_FOUND, file ); } final RepositoryFileDto fileDto = fileInfo.getFile(); if ( fileDto == null ) { throw new FileSystemException( BI_SERVER_NULL_OBJECT ); } return fileDto.isFolder(); } public boolean exists( final FileName file ) throws FileSystemException { final String[] fileName = computeFileNames( file ); final RepositoryFileTreeDto fileInfo = lookupNode( fileName ); return ( fileInfo != null ); } public boolean isVisible( final FileName file ) throws FileSystemException { final String[] fileName = computeFileNames( file ); final RepositoryFileTreeDto fileInfo = lookupNode( fileName ); if ( fileInfo == null ) { throw new FileSystemException( FILE_NOT_FOUND, file ); } final RepositoryFileDto fileDto = fileInfo.getFile(); if ( fileDto == null ) { throw new FileSystemException( BI_SERVER_NULL_OBJECT ); } return !fileDto.isHidden(); } public void setDescription( final FileName file, final String description ) throws FileSystemException { final String[] fileName = computeFileNames( file ); final RepositoryFileTreeDto fileInfo = lookupNode( fileName ); if ( fileInfo == null ) { throw new FileSystemException( FILE_NOT_FOUND, file ); } final RepositoryFileDto fileDto = fileInfo.getFile(); if ( fileDto == null ) { throw new FileSystemException( BI_SERVER_NULL_OBJECT ); } fileDto.setDescription( description ); } public String getDescription( final FileName file ) throws FileSystemException { final String[] fileName = computeFileNames( file ); final RepositoryFileTreeDto fileInfo = lookupNode( fileName ); if ( fileInfo == null ) { throw new FileSystemException( FILE_NOT_FOUND, file ); } final RepositoryFileDto fileDto = fileInfo.getFile(); if ( fileDto == null ) { throw new FileSystemException( BI_SERVER_NULL_OBJECT ); } return fileDto.getDescription(); } public long getLastModifiedDate( final FileName file ) throws FileSystemException { final String[] fileName = computeFileNames( file ); final RepositoryFileTreeDto fileInfo = lookupNode( fileName ); if ( fileInfo == null ) { throw new FileSystemException( FILE_NOT_FOUND, file ); } final RepositoryFileDto fileDto = fileInfo.getFile(); if ( fileDto == null ) { throw new FileSystemException( BI_SERVER_NULL_OBJECT ); } final Date lastModifiedDate = fileDto.getLastModifiedDate(); if ( lastModifiedDate == null ) { logger.error( "Repository returned <null> for last-modified-date on file: " + file ); return -1; } return lastModifiedDate.getTime(); } private String getFormattedServiceUrl( final String urlService, final FileName file ) throws FileSystemException { if ( urlService == null ) { throw new NullPointerException(); } final String[] fileName = computeFileNames( file ); final RepositoryFileTreeDto fileInfo = lookupNode( fileName ); if ( fileInfo == null ) { throw new FileSystemException( FILE_NOT_FOUND, file ); } final String restName = normalizePath( file.getPath() ); return MessageFormat .format( urlService, URLEncoder.encodeUTF8( restName ).replaceAll( "\\!", "%21" ).replaceAll( "\\+", "%2B" ) ); } public String getUrl( final FileName file ) throws FileSystemException { return getFormattedServiceUrl( RETRIEVE_CONTENT_SERVICE, file ); } public List<RepositoryFileTreeDto> getChildren( final FileName parent ) throws FileSystemException { final String[] pathArray = computeFileNames( parent ); final RepositoryFileTreeDto fileInfo = lookupNode( pathArray ); if ( fileInfo == null ) { throw new FileSystemException( FILE_NOT_FOUND, parent ); } final List<RepositoryFileTreeDto> childNodes = fileInfo.getChildren(); return childNodes == null ? Collections.<RepositoryFileTreeDto>emptyList() : childNodes; } protected RepositoryFileTreeDto lookupNode( final String[] path ) throws FileSystemException { if ( root == null ) { try { refresh(); } catch ( IOException e ) { throw new FileSystemException( e ); } } if ( path.length == 0 ) { return root; } if ( "".equals( path[ 0 ] ) ) { if ( path.length == 1 ) { return root; } } RepositoryFileTreeDto element = root; for ( final String pathSegment : path ) { RepositoryFileTreeDto name = null; final List<RepositoryFileTreeDto> children = element.getChildren(); if ( children == null ) { return null; } for ( final RepositoryFileTreeDto child : children ) { final RepositoryFileDto file = child.getFile(); if ( file == null ) { throw new FileSystemException( BI_SERVER_NULL_OBJECT ); } if ( pathSegment.equals( file.getName() ) ) { name = child; break; } } if ( name == null ) { return null; } element = name; } return element; } protected String[] computeFileNames( FileName file ) { final FastStack<String> stack = new FastStack<String>(); while ( file != null ) { String name; try { name = URLDecoder.decode( file.getBaseName().trim().replaceAll( "\\+", "%2B" ), "UTF-8" ); } catch ( UnsupportedEncodingException e ) { name = file.getBaseName().trim(); } if ( !"".equals( name ) ) { stack.push( name ); } file = file.getParent(); } final int size = stack.size(); final String[] result = new String[ size ]; for ( int i = 0; i < result.length; i++ ) { result[ i ] = stack.pop(); } return result; } public byte[] getData( final FileName file ) throws FileSystemException { final String[] fileName = computeFileNames( file ); final RepositoryFileTreeDto fileInfo = lookupNode( fileName ); if ( fileInfo == null ) { throw new FileSystemException( FILE_NOT_FOUND, file ); } final RepositoryFileDto fileDto = fileInfo.getFile(); if ( fileDto == null ) { throw new IllegalStateException( BI_SERVER_NULL_OBJECT ); } final String path = normalizePath( fileDto.getPath() ); String urlPath = path; try { urlPath = URLEncoder.encodeUTF8( path ).replaceAll( "\\!", "%21" ).replaceAll( "\\+", "%2B" ); } catch ( Exception ex ) { } //tcb final String service = MessageFormat.format( DOWNLOAD_SERVICE, urlPath ); return client.resource( url + service ).accept( MediaType.APPLICATION_XML_TYPE ).get( byte[].class ); } public void setData( final FileName file, final byte[] data ) throws FileSystemException { final String[] fileName = computeFileNames( file ); final StringBuilder b = new StringBuilder(); for ( int i = 0; i < fileName.length; i++ ) { if ( i != 0 ) { b.append( SLASH ); } b.append( fileName[ i ] ); } String service = MessageFormat.format( UPLOAD_SERVICE, URLEncoder.encodeUTF8( normalizePath( b.toString() ).replaceAll( "\\!", "%21" ).replaceAll( "\\+", "%2B" ) ) ); final WebResource resource = client.resource( url + service ); final ByteArrayInputStream stream = new ByteArrayInputStream( data ); final ClientResponse response = resource.put( ClientResponse.class, stream ); final int status = response.getStatus(); if ( status != HttpStatus.SC_OK ) { if ( status == HttpStatus.SC_MOVED_TEMPORARILY || status == HttpStatus.SC_FORBIDDEN || status == HttpStatus.SC_UNAUTHORIZED ) { throw new FileSystemException( "ERROR_INVALID_USERNAME_OR_PASSWORD" ); } else { throw new FileSystemException( "ERROR_FAILED", status ); } } try { // Perhaps a New file was created. Refresh local tree model. this.refresh(); } catch ( IOException e ) { // Ignore if unable to refresh } } public String[] getChilds( final FileName name ) throws FileSystemException { final List<RepositoryFileTreeDto> children = getChildren( name ); final String[] childrenArray = new String[ children.size() ]; for ( int i = 0; i < children.size(); i++ ) { final RepositoryFileTreeDto repositoryFileTreeDto = children.get( i ); if ( repositoryFileTreeDto == null ) { continue; } final RepositoryFileDto file = repositoryFileTreeDto.getFile(); if ( file == null ) { throw new FileSystemException( BI_SERVER_NULL_OBJECT ); } childrenArray[ i ] = file.getName().replaceAll( "\\%", "%25" ).replaceAll( "\\!", "%21" ).replaceAll( "\\+", "%2B" ); } return childrenArray; } public String getLocalizedName( final FileName file ) throws FileSystemException { final String[] fileName = computeFileNames( file ); final RepositoryFileTreeDto fileInfo = lookupNode( fileName ); if ( fileInfo == null ) { throw new FileSystemException( FILE_NOT_FOUND, file ); } final RepositoryFileDto fileDto = fileInfo.getFile(); if ( fileDto == null ) { throw new FileSystemException( BI_SERVER_NULL_OBJECT ); } return fileDto.getTitle(); } public String getParamServiceUrl( final FileName name ) throws FileSystemException { return url + getFormattedServiceUrl( RETRIEVE_PARAMETER_URL_SERVICE, name ); } public long getRefreshTime() { return refreshTime; } public void setRefreshTime( final long refreshTime ) { this.refreshTime = refreshTime; } public HashMap<FileName, String> getDescriptionEntries() { return descriptionEntries; } public String getMajorVersion() { return majorVersion; } public String getMinorVersion() { return minorVersion; } public String getReleaseVersion() { return releaseVersion; } public String getBuildVersion() { return buildVersion; } public String getMilestoneVersion() { return milestoneVersion; } public long getContentSize( final FileName name ) throws FileSystemException { final String[] pathArray = computeFileNames( name ); final RepositoryFileTreeDto fileInfo = lookupNode( pathArray ); if ( fileInfo == null ) { throw new FileSystemException( FILE_NOT_FOUND, name ); } final RepositoryFileDto file = fileInfo.getFile(); if ( file == null ) { throw new FileSystemException( BI_SERVER_NULL_OBJECT ); } return file.getFileSize(); } @Override public boolean delete( FileName name ) throws FileSystemException { boolean success = false; final RepositoryFileDto file = getFile( name ); try { final WebResource resource = client.resource( url + DELETE_FILE_OR_FOLDER ); final ClientResponse response = resource.put( ClientResponse.class, file.getId() ); if ( response != null && response.getStatus() == Response.Status.OK.getStatusCode() ) { refresh(); success = true; } else { throw new FileSystemException( "Failed with error-code " + response.getStatus() ); } } catch ( Exception e ) { throw new FileSystemException( "Failed", e ); } return success; } private RepositoryFileDto getFile( FileName name ) throws FileSystemException { if ( name == null ) { throw new FileSystemException( FILE_NOT_FOUND ); } final String[] pathArray = computeFileNames( name ); final RepositoryFileTreeDto fileInfo = lookupNode( pathArray ); if ( fileInfo == null ) { throw new FileSystemException( FILE_NOT_FOUND, name ); } final RepositoryFileDto file = fileInfo.getFile(); if ( file == null ) { throw new FileSystemException( BI_SERVER_NULL_OBJECT ); } return file; } }