/*!
* 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 );
}
}
}