/*! * 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.designer.extensions.pentaho.drilldown; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.auth.AuthScope; import org.apache.commons.httpclient.cookie.CookiePolicy; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.commons.vfs2.FileContent; import org.apache.commons.vfs2.FileObject; import org.apache.commons.vfs2.FileSystem; import org.apache.commons.vfs2.FileSystemException; import org.apache.commons.vfs2.VFS; import org.pentaho.reporting.designer.core.ReportDesignerContext; import org.pentaho.reporting.designer.core.auth.AuthenticationData; import org.pentaho.reporting.designer.core.auth.AuthenticationHelper; import org.pentaho.reporting.designer.core.editor.ReportDocumentContext; import org.pentaho.reporting.designer.core.editor.drilldown.DrillDownParameterRefreshEvent; import org.pentaho.reporting.designer.core.editor.drilldown.DrillDownParameterRefreshListener; import org.pentaho.reporting.designer.core.editor.drilldown.DrillDownParameterTable; import org.pentaho.reporting.designer.core.editor.drilldown.model.DrillDownParameter; import org.pentaho.reporting.designer.core.editor.drilldown.model.Parameter; import org.pentaho.reporting.designer.core.editor.drilldown.model.ParameterDocument; import org.pentaho.reporting.designer.core.settings.WorkspaceSettings; import org.pentaho.reporting.designer.core.util.exceptions.UncaughtExceptionsModel; import org.pentaho.reporting.designer.extensions.pentaho.repository.actions.AuthenticatedServerTask; import org.pentaho.reporting.designer.extensions.pentaho.repository.actions.LoginTask; import org.pentaho.reporting.designer.extensions.pentaho.repository.util.PublishException; import org.pentaho.reporting.designer.extensions.pentaho.repository.util.PublishUtil; import org.pentaho.reporting.engine.classic.core.parameters.ParameterAttributeNames; import org.pentaho.reporting.libraries.base.util.IOUtils; import org.pentaho.reporting.libraries.base.util.StringUtils; import org.pentaho.reporting.libraries.designtime.swing.background.BackgroundCancellableProcessHelper; import org.pentaho.reporting.libraries.designtime.swing.background.GenericCancelHandler; import org.pentaho.reporting.libraries.formatting.FastMessageFormat; import org.pentaho.reporting.libraries.pensol.vfs.WebSolutionFileSystem; import org.pentaho.reporting.libraries.resourceloader.Resource; import org.pentaho.reporting.libraries.resourceloader.ResourceManager; import java.awt.Component; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import javax.swing.SwingUtilities; public class PentahoParameterRefreshHandler implements DrillDownParameterRefreshListener { private class UpdateRequestParamsTask implements AuthenticatedServerTask { private RequestParamsFromServerTask requestParamsFromServerTask; private Component uiContext; private AuthenticationData loginData; private DrillDownParameter[] existingParameters; private UpdateRequestParamsTask( final RequestParamsFromServerTask requestParamsFromServerTask, final Component uiContext, final DrillDownParameterRefreshEvent event ) { this.requestParamsFromServerTask = requestParamsFromServerTask; this.uiContext = uiContext; this.existingParameters = event.getParameter(); } public void setLoginData( final AuthenticationData loginData, final boolean storeUpdate ) { this.loginData = loginData; requestParamsFromServerTask.setLoginData( loginData, storeUpdate ); } /** * When an object implementing interface <code>Runnable</code> is used to create a thread, starting the thread * causes the object's <code>run</code> method to be called in that separately executing thread. * <p/> * The general contract of the method <code>run</code> is that it may take any action whatsoever. * * @see Thread#run() */ public void run() { if ( loginData == null ) { return; } final ReportDocumentContext reportRenderContext = reportDesignerContext.getActiveContext(); final Object o = reportRenderContext.getProperties().get( "pentaho-login-url" ); if ( o == null ) { reportRenderContext.getProperties().put( "pentaho-login-url", loginData.getUrl() ); } final Thread loginThread = new Thread( requestParamsFromServerTask ); loginThread.setName( "Request-parameter-from-server" ); loginThread.setDaemon( true ); loginThread.setPriority( Thread.MIN_PRIORITY ); final GenericCancelHandler cancelHandler = new GenericCancelHandler( loginThread ); BackgroundCancellableProcessHelper.executeProcessWithCancelDialog( loginThread, cancelHandler, uiContext, "Requesting Parameter Information .." ); if ( cancelHandler.isCancelled() ) { return; } final Exception error = requestParamsFromServerTask.getError(); if ( error != null ) { UncaughtExceptionsModel.getInstance().addException( error ); logger.warn( "Failed to read parameter from server", error ); return; } if ( requestParamsFromServerTask.getParameters() == null ) { logger.warn( "Failed to read parameter from server: File does not exist." ); return; } final Parameter[] parameters = requestParamsFromServerTask.getParameters(); final HashMap<String, DrillDownParameter> parameterFromServer = new HashMap<String, DrillDownParameter>(); final ArrayList<DrillDownParameter> drillDownParameters = new ArrayList<DrillDownParameter>(); for ( int i = 0; i < parameters.length; i++ ) { final Parameter parameter = parameters[i]; final DrillDownParameter drillDownParameter = new DrillDownParameter( parameter.getName() ); if ( "system".equals( parameter.getAttribute( ParameterAttributeNames.Core.PARAMETER_GROUP ) ) || "subscription".equals( parameter.getAttribute( ParameterAttributeNames.Core.PARAMETER_GROUP ) ) || "output-target".equals( parameter.getName() ) ) { drillDownParameter.setType( DrillDownParameter.Type.SYSTEM ); } else { drillDownParameter.setType( DrillDownParameter.Type.PREDEFINED ); } if ( "false".equals( parameter.getAttribute( ParameterAttributeNames.Core.NAMESPACE, ParameterAttributeNames.Core.PREFERRED ) ) ) { drillDownParameter.setPreferred( false ); } else { drillDownParameter.setPreferred( true ); } drillDownParameters.add( drillDownParameter ); parameterFromServer.put( parameter.getName(), drillDownParameter ); } for ( int i = 0; i < existingParameters.length; i++ ) { final DrillDownParameter parameter = existingParameters[i]; if ( parameterFromServer.containsKey( parameter.getName() ) ) { // this parameter also exists in the listing from the server, so rescue its contents final DrillDownParameter existingOne = parameterFromServer.get( parameter.getName() ); existingOne.setFormulaFragment( parameter.getFormulaFragment() ); existingParameters[i] = null; } } // finally add all left-over parameters as manual parameters. for ( int i = 0; i < existingParameters.length; i++ ) { final DrillDownParameter parameter = existingParameters[i]; if ( parameter == null ) { continue; } parameter.setType( DrillDownParameter.Type.MANUAL ); parameter.setPosition( drillDownParameters.size() ); drillDownParameters.add( parameter ); } final DrillDownParameter[] computedSet = drillDownParameters.toArray( new DrillDownParameter[drillDownParameters.size()] ); if ( parameterTable != null ) { parameterTable.setDrillDownParameter( computedSet ); } } } public static HttpClient createHttpClient( final AuthenticationData loginData ) { final HttpClient client = new HttpClient(); client.getParams().setCookiePolicy( CookiePolicy.BROWSER_COMPATIBILITY ); client.getParams().setSoTimeout( WorkspaceSettings.getInstance().getConnectionTimeout() * 1000 ); client.getParams().setAuthenticationPreemptive( true ); client.getState().setCredentials( AuthScope.ANY, AuthenticationHelper.getCredentials( loginData.getUsername(), loginData.getPassword() ) ); return client; } private static class RequestParamsFromServerTask implements AuthenticatedServerTask { private AuthenticationData loginData; private Exception error; private Parameter[] parameters; private PentahoPathModel pathModel; private RequestParamsFromServerTask( final PentahoPathModel pathModel ) { this.pathModel = pathModel; } public void setLoginData( final AuthenticationData loginData, final boolean storeUpdates ) { this.loginData = loginData; } public Exception getError() { return error; } public Parameter[] getParameters() { return parameters; } /** * When an object implementing interface <code>Runnable</code> is used to create a thread, starting the thread * causes the object's <code>run</code> method to be called in that separately executing thread. * <p/> * The general contract of the method <code>run</code> is that it may take any action whatsoever. * * @see Thread#run() */ public void run() { pathModel.setLoginData( loginData ); final String paramServiceText = getParameterServicePath( loginData, pathModel ); if ( paramServiceText == null ) { return; } try { final HttpClient httpClient = createHttpClient( loginData ); final GetMethod method = new GetMethod( paramServiceText ); method.setFollowRedirects( false ); final int result = httpClient.executeMethod( method ); if ( result != HttpStatus.SC_OK ) { if ( result == HttpStatus.SC_MOVED_TEMPORARILY || result == HttpStatus.SC_FORBIDDEN || result == HttpStatus.SC_UNAUTHORIZED ) { // notify the world that the login data is no longer valid throw new PublishException( PublishException.ERROR_INVALID_USERNAME_OR_PASSWORD ); } else { throw new PublishException( PublishException.ERROR_FAILED, result ); } } final byte[] responseBody = method.getResponseBody(); final ResourceManager manager = new ResourceManager(); final Resource resource = manager.createDirectly( responseBody, ParameterDocument.class ); final ParameterDocument o = (ParameterDocument) resource.getResource(); parameters = o.getParameter(); } catch ( Exception e ) { error = e; } } } private static final Log logger = LogFactory.getLog( PentahoParameterRefreshHandler.class ); private PentahoPathModel pathModel; private DrillDownParameterTable parameterTable; private ReportDesignerContext reportDesignerContext; private Component component; public PentahoParameterRefreshHandler( final PentahoPathModel pentahoPathWrapper, final ReportDesignerContext reportDesignerContext, final Component component ) { if ( pentahoPathWrapper == null ) { throw new NullPointerException(); } if ( reportDesignerContext == null ) { throw new NullPointerException(); } if ( component == null ) { throw new NullPointerException(); } this.reportDesignerContext = reportDesignerContext; this.component = component; this.pathModel = pentahoPathWrapper; } public PentahoPathModel getPathModel() { return pathModel; } public DrillDownParameterTable getParameterTable() { return parameterTable; } public void setParameterTable( final DrillDownParameterTable parameterTable ) { this.parameterTable = parameterTable; } private static String getParameterServicePath( final AuthenticationData loginData, final PentahoPathModel pathModel ) { try { final FileObject fileSystemRoot = PublishUtil.createVFSConnection( VFS.getManager(), loginData ); final FileSystem fileSystem = fileSystemRoot.getFileSystem(); // as of version 3.7 we do not need to check anything other than that the version information is there // later we may have to add additional checks in here to filter out known broken versions. final String localPath = pathModel.getLocalPath(); final FileObject object = fileSystemRoot.resolveFile( localPath ); final FileContent content = object.getContent(); final String majorVersionText = (String) fileSystem.getAttribute( WebSolutionFileSystem.MAJOR_VERSION ); if ( StringUtils.isEmpty( majorVersionText ) == false ) { final String paramService = (String) content.getAttribute( "param-service-url" ); if ( StringUtils.isEmpty( paramService ) ) { return null; } if ( paramService.startsWith( "http://" ) || paramService.startsWith( "https://" ) ) { return paramService; } try { // Encode the URL (must use URI as URL encoding doesn't work on spaces correctly) final URL target = new URL( loginData.getUrl() ); final String host; if ( target.getPort() != -1 ) { host = target.getHost() + ":" + target.getPort(); } else { host = target.getHost(); } return target.getProtocol() + "://" + host + paramService; } catch ( MalformedURLException e ) { UncaughtExceptionsModel.getInstance().addException( e ); return null; } } final String extension = IOUtils.getInstance().getFileExtension( localPath ); if ( ".prpt".equals( extension ) ) { logger.debug( "Ancient pentaho system detected: parameter service does not deliver valid parameter values" ); final String name = pathModel.getName(); final String path = pathModel.getPath(); final String solution = pathModel.getSolution(); final FastMessageFormat messageFormat = new FastMessageFormat( "/content/reporting/?renderMode=XML&solution={0}&path={1}&name={2}" ); messageFormat.setNullString( "" ); return loginData.getUrl() + messageFormat.format( new Object[] { solution, path, name } ); } logger.debug( "Ancient pentaho system detected: We will not have access to a working parameter service" ); return null; } catch ( FileSystemException e ) { UncaughtExceptionsModel.getInstance().addException( e ); return null; } } public void requestParameterRefresh( final DrillDownParameterRefreshEvent event ) { final String localPath = pathModel.getLocalPath(); if ( StringUtils.isEmpty( localPath ) ) { return; } try { final RequestParamsFromServerTask requestParamsFromServerTask = new RequestParamsFromServerTask( pathModel ); final LoginTask loginTask = new LoginTask( reportDesignerContext, component, new UpdateRequestParamsTask( requestParamsFromServerTask, component, event ), pathModel.getLoginData() ); SwingUtilities.invokeLater( loginTask ); } catch ( Exception e ) { UncaughtExceptionsModel.getInstance().addException( e ); logger.warn( "Failed to access parameter system", e ); } } }