/*!
* 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.ui.datasources.scriptable;
import org.apache.bsf.BSFManager;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rtextarea.RTextScrollPane;
import org.pentaho.reporting.engine.classic.core.ReportDataFactoryException;
import org.pentaho.reporting.engine.classic.core.designtime.DesignTimeContext;
import org.pentaho.reporting.engine.classic.core.designtime.datafactory.DataFactoryEditorSupport;
import org.pentaho.reporting.engine.classic.core.modules.gui.commonswing.ExceptionDialog;
import org.pentaho.reporting.engine.classic.core.util.ReportParameterValues;
import org.pentaho.reporting.engine.classic.extensions.datasources.scriptable.ScriptableDataFactory;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.libraries.designtime.swing.BorderlessButton;
import org.pentaho.reporting.libraries.designtime.swing.CommonDialog;
import org.pentaho.reporting.libraries.designtime.swing.background.CancelEvent;
import org.pentaho.reporting.libraries.designtime.swing.background.DataPreviewDialog;
import org.pentaho.reporting.libraries.designtime.swing.background.PreviewWorker;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
/**
* @author David Kincade
*/
public class ScriptableDataSourceEditor extends CommonDialog {
private class UpdateLanguageHandler implements ActionListener, ListSelectionListener {
private UpdateLanguageHandler() {
}
public void actionPerformed( final ActionEvent e ) {
updateComponents();
}
/**
* Called whenever the value of the selection changes.
*
* @param e the event that characterizes the change.
*/
public void valueChanged( final ListSelectionEvent e ) {
updateComponents();
}
}
private static class InternalBSFManager extends BSFManager {
private InternalBSFManager() {
}
public static String[] getRegisteredLanguages() {
final ArrayList<String> list = new ArrayList<String>();
final Iterator iterator = registeredEngines.entrySet().iterator();
while ( iterator.hasNext() ) {
final Map.Entry entry = (Map.Entry) iterator.next();
final String lang = (String) entry.getKey();
final String className = (String) entry.getValue();
try {
// this is how BSH will load the class
Class.forName( className, false, Thread.currentThread().getContextClassLoader() );
list.add( lang );
} catch ( Throwable t ) {
// ignored.
}
}
return list.toArray( new String[ list.size() ] );
}
}
private class QueryRemoveAction extends AbstractAction implements ListSelectionListener {
private QueryRemoveAction() {
final URL resource = ScriptableDataSourceEditor.class.getResource
( "/org/pentaho/reporting/ui/datasources/scriptable/resources/Remove.png" );
if ( resource != null ) {
putValue( Action.SMALL_ICON, new ImageIcon( resource ) );
} else {
putValue( Action.NAME, Messages.getString( "ScriptableDataSourceEditor.RemoveQuery.Name" ) );
}
putValue( Action.SHORT_DESCRIPTION, Messages.getString( "ScriptableDataSourceEditor.RemoveQuery.Description" ) );
}
public void actionPerformed( final ActionEvent e ) {
final DataSetQuery query = (DataSetQuery) queryNameList.getSelectedValue();
if ( query != null ) {
queries.remove( query.getQueryName() );
}
inModifyingQueryNameList = true;
updateQueryList();
queryNameList.clearSelection();
inModifyingQueryNameList = false;
updateComponents();
}
public void valueChanged( final ListSelectionEvent e ) {
setEnabled( queryNameList.isSelectionEmpty() == false );
}
}
private class QueryNameTextFieldDocumentListener implements DocumentListener {
public void insertUpdate( final DocumentEvent e ) {
update();
}
public void removeUpdate( final DocumentEvent e ) {
update();
}
public void changedUpdate( final DocumentEvent e ) {
update();
}
private void update() {
if ( inModifyingQueryNameList ) {
return;
}
final String queryName = queryNameTextField.getText();
final DataSetQuery currentQuery = (DataSetQuery) queryNameList.getSelectedValue();
if ( currentQuery == null ) {
return;
}
if ( queryName.equals( currentQuery.getQueryName() ) ) {
return;
}
if ( queries.containsKey( queryName ) ) {
return;
}
inQueryNameUpdate = true;
queries.remove( currentQuery.getQueryName() );
currentQuery.setQueryName( queryName );
queries.put( currentQuery.getQueryName(), currentQuery );
updateQueryList();
queryNameList.setSelectedValue( currentQuery, true );
inQueryNameUpdate = false;
}
}
private static class QueryNameListCellRenderer extends DefaultListCellRenderer {
public Component getListCellRendererComponent( final JList list,
final Object value,
final int index,
final boolean isSelected,
final boolean cellHasFocus ) {
final JLabel listCellRendererComponent =
(JLabel) super.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus );
if ( value != null ) {
final String queryName = ( (DataSetQuery) value ).getQueryName();
if ( StringUtils.isEmpty( queryName ) == false ) {
listCellRendererComponent.setText( queryName );
} else {
listCellRendererComponent.setText( " " );
}
}
return listCellRendererComponent;
}
}
private class QueryNameListSelectionListener implements ListSelectionListener {
public void valueChanged( final ListSelectionEvent e ) {
if ( !inQueryNameUpdate ) {
final DataSetQuery query = (DataSetQuery) queryNameList.getSelectedValue();
if ( query != null ) {
queryNameTextField.setText( query.getQueryName() );
queryTextArea.setText( query.getQuery() );
updateComponents();
} else {
queryNameTextField.setText( "" );
queryTextArea.setText( "" );
updateComponents();
}
}
}
}
private class QueryAddAction extends AbstractAction {
private QueryAddAction() {
final URL resource = ScriptableDataSourceEditor.class.getResource
( "/org/pentaho/reporting/ui/datasources/scriptable/resources/Add.png" );
if ( resource != null ) {
putValue( Action.SMALL_ICON, new ImageIcon( resource ) );
} else {
putValue( Action.NAME, Messages.getString( "ScriptableDataSourceEditor.AddQuery.Name" ) );
}
putValue( Action.SHORT_DESCRIPTION, Messages.getString( "ScriptableDataSourceEditor.AddQuery.Description" ) );
}
public void actionPerformed( final ActionEvent e ) {
// Find a unique query name
String queryName = Messages.getString( "ScriptableDataSourceEditor.Query" );
for ( int i = 1; i < 1000; ++i ) {
final String newQueryName = Messages.getString( "ScriptableDataSourceEditor.Query" ) + ' ' + i;
if ( !queries.containsKey( newQueryName ) ) {
queryName = newQueryName;
break;
}
}
final DataSetQuery newQuery = new DataSetQuery( queryName, "" );
queries.put( newQuery.getQueryName(), newQuery );
inModifyingQueryNameList = true;
updateQueryList();
queryNameList.setSelectedValue( newQuery, true );
inModifyingQueryNameList = false;
updateComponents();
}
}
private class QueryDocumentListener implements DocumentListener {
private QueryDocumentListener() {
}
public void insertUpdate( final DocumentEvent e ) {
update();
}
public void removeUpdate( final DocumentEvent e ) {
update();
}
public void changedUpdate( final DocumentEvent e ) {
update();
}
private void update() {
final DataSetQuery currentQuery = (DataSetQuery) queryNameList.getSelectedValue();
if ( currentQuery == null ) {
return;
}
currentQuery.setQuery( queryTextArea.getText() );
}
}
private class PreviewAction extends AbstractAction {
private PreviewAction() {
putValue( Action.NAME, Messages.getString( "ScriptableDataSourceEditor.Preview.Name" ) );
}
public void actionPerformed( final ActionEvent aEvt ) {
try {
final ScriptableDataFactory dataFactory = produceFactory();
DataFactoryEditorSupport.configureDataFactoryForPreview( dataFactory, designTimeContext );
final DataPreviewDialog previewDialog = new DataPreviewDialog( ScriptableDataSourceEditor.this );
final ScriptablePreviewWorker worker = new ScriptablePreviewWorker( dataFactory, queryNameTextField.getText() );
previewDialog.showData( worker );
final ReportDataFactoryException factoryException = worker.getException();
if ( factoryException != null ) {
ExceptionDialog.showExceptionDialog( ScriptableDataSourceEditor.this,
Messages.getString( "ScriptableDataSourceEditor.PreviewError.Title" ),
Messages.getString( "ScriptableDataSourceEditor.PreviewError.Message" ), factoryException );
}
} catch ( Exception e ) {
ExceptionDialog.showExceptionDialog( ScriptableDataSourceEditor.this,
Messages.getString( "ScriptableDataSourceEditor.PreviewError.Title" ),
Messages.getString( "ScriptableDataSourceEditor.PreviewError.Message" ), e );
}
}
}
private static class ScriptablePreviewWorker implements PreviewWorker {
private ScriptableDataFactory dataFactory;
private TableModel resultTableModel;
private ReportDataFactoryException exception;
private String query;
private ScriptablePreviewWorker( final ScriptableDataFactory dataFactory,
final String query ) {
if ( dataFactory == null ) {
throw new NullPointerException();
}
this.query = query;
this.dataFactory = dataFactory;
}
public ReportDataFactoryException getException() {
return exception;
}
public TableModel getResultTableModel() {
return resultTableModel;
}
public void close() {
}
/**
* Requests that the thread stop processing as soon as possible.
*/
public void cancelProcessing( final CancelEvent event ) {
dataFactory.cancelRunningQuery();
}
/**
* 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() {
try {
resultTableModel = dataFactory.queryData( query, new ReportParameterValues() );
} catch ( ReportDataFactoryException e ) {
exception = e;
} finally {
dataFactory.close();
}
}
}
private JList queryNameList;
private JTextField queryNameTextField;
private JList languageField;
private RSyntaxTextArea queryTextArea;
private RSyntaxTextArea initScriptTextArea;
private RSyntaxTextArea shutdownScriptTextArea;
private Map<String, DataSetQuery> queries;
private boolean inQueryNameUpdate;
private boolean inModifyingQueryNameList;
private PreviewAction previewAction;
private DesignTimeContext designTimeContext;
public ScriptableDataSourceEditor( final DesignTimeContext designTimeContext ) {
init( designTimeContext );
}
public ScriptableDataSourceEditor( final DesignTimeContext designTimeContext, final Dialog owner ) {
super( owner );
init( designTimeContext );
}
public ScriptableDataSourceEditor( final DesignTimeContext designTimeContext, final Frame owner ) {
super( owner );
init( designTimeContext );
}
private void init( final DesignTimeContext designTimeContext ) {
if ( designTimeContext == null ) {
throw new NullPointerException();
}
this.designTimeContext = designTimeContext;
setTitle( Messages.getString( "ScriptableDataSourceEditor.Title" ) );
setModal( true );
previewAction = new PreviewAction();
queryNameTextField = new JTextField( null, 0 );
queryNameTextField.setColumns( 35 );
queryNameTextField.getDocument().addDocumentListener( new QueryNameTextFieldDocumentListener() );
queryTextArea = new RSyntaxTextArea();
queryTextArea.setSyntaxEditingStyle( SyntaxConstants.SYNTAX_STYLE_NONE );
queryTextArea.getDocument().addDocumentListener( new QueryDocumentListener() );
initScriptTextArea = new RSyntaxTextArea();
initScriptTextArea.setSyntaxEditingStyle( SyntaxConstants.SYNTAX_STYLE_NONE );
shutdownScriptTextArea = new RSyntaxTextArea();
shutdownScriptTextArea.setSyntaxEditingStyle( SyntaxConstants.SYNTAX_STYLE_NONE );
languageField = new JList( new DefaultComboBoxModel( InternalBSFManager.getRegisteredLanguages() ) );
languageField.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
languageField.getSelectionModel().addListSelectionListener( new UpdateLanguageHandler() );
queryNameList = new JList();
queryNameList.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
queryNameList.setVisibleRowCount( 5 );
queryNameList.addListSelectionListener( new QueryNameListSelectionListener() );
queryNameList.setCellRenderer( new QueryNameListCellRenderer() );
final QueryRemoveAction removeQueryAction = new QueryRemoveAction();
queryNameList.addListSelectionListener( removeQueryAction );
super.init();
}
protected String getDialogId() {
return "ScriptableDataSourceEditor";
}
protected Component createContentPane() {
final JPanel initScriptContentHolder = new JPanel( new BorderLayout() );
initScriptContentHolder
.add( BorderLayout.NORTH, new JLabel( Messages.getString( "ScriptableDataSourceEditor.InitScript" ) ) );
initScriptContentHolder.add( BorderLayout.CENTER, new RTextScrollPane( 500, 600, initScriptTextArea, true ) );
final JPanel shutdownScriptContentHolder = new JPanel( new BorderLayout() );
shutdownScriptContentHolder
.add( BorderLayout.NORTH, new JLabel( Messages.getString( "ScriptableDataSourceEditor.ShutdownScript" ) ) );
shutdownScriptContentHolder
.add( BorderLayout.CENTER, new RTextScrollPane( 500, 600, shutdownScriptTextArea, true ) );
final JPanel queryDetailsNamePanel = new JPanel( new BorderLayout() );
queryDetailsNamePanel
.add( new JLabel( Messages.getString( "ScriptableDataSourceEditor.QueryName" ) ), BorderLayout.NORTH );
queryDetailsNamePanel.add( queryNameTextField, BorderLayout.CENTER );
final JPanel queryContentHolder = new JPanel( new BorderLayout() );
queryContentHolder
.add( BorderLayout.NORTH, new JLabel( Messages.getString( "ScriptableDataSourceEditor.QueryLabel" ) ) );
queryContentHolder.add( BorderLayout.CENTER, new RTextScrollPane( 500, 300, queryTextArea, true ) );
// Create the query details panel
final JPanel queryDetailsPanel = new JPanel( new BorderLayout() );
queryDetailsPanel.setBorder( new EmptyBorder( 0, 8, 8, 8 ) );
queryDetailsPanel.add( BorderLayout.NORTH, queryDetailsNamePanel );
queryDetailsPanel.add( BorderLayout.CENTER, queryContentHolder );
final JPanel previewButtonPanel = new JPanel( new FlowLayout( FlowLayout.RIGHT ) );
previewButtonPanel.add( new JButton( previewAction ) );
final JPanel queryContentPanel = new JPanel( new BorderLayout() );
queryContentPanel.add( BorderLayout.NORTH, createQueryListPanel() );
queryContentPanel.add( BorderLayout.CENTER, queryDetailsPanel );
final JTabbedPane scriptsTabPane = new JTabbedPane();
scriptsTabPane.addTab( Messages.getString( "ScriptableDataSourceEditor.QueryTab" ), queryContentPanel );
scriptsTabPane.addTab( Messages.getString( "ScriptableDataSourceEditor.InitScriptTab" ), initScriptContentHolder );
scriptsTabPane
.addTab( Messages.getString( "ScriptableDataSourceEditor.ShutdownScriptTab" ), shutdownScriptContentHolder );
final JLabel languageLabel = new JLabel( Messages.getString( "ScriptableDataSourceEditor.Language" ) );
languageLabel.setBorder( new EmptyBorder( 0, 0, 3, 0 ) );
final JPanel languagesPanel = new JPanel( new BorderLayout() );
languagesPanel.setBorder( new EmptyBorder( 8, 8, 8, 0 ) );
languagesPanel.add( BorderLayout.NORTH, languageLabel );
languagesPanel.add( BorderLayout.CENTER, new JScrollPane( languageField ) );
final JPanel contentPanel = new JPanel( new BorderLayout() );
contentPanel.add( BorderLayout.WEST, languagesPanel );
contentPanel.add( BorderLayout.CENTER, scriptsTabPane );
contentPanel.add( BorderLayout.SOUTH, previewButtonPanel );
return contentPanel;
}
private JPanel createQueryListPanel() {
final QueryRemoveAction queryRemoveAction = new QueryRemoveAction();
queryNameList.addListSelectionListener( queryRemoveAction );
final JPanel theQueryButtonsPanel = new JPanel( new FlowLayout( FlowLayout.RIGHT ) );
theQueryButtonsPanel.add( new BorderlessButton( new QueryAddAction() ) );
theQueryButtonsPanel.add( new BorderlessButton( queryRemoveAction ) );
final JPanel theQueryControlsPanel = new JPanel( new BorderLayout() );
theQueryControlsPanel
.add( BorderLayout.WEST, new JLabel( Messages.getString( "ScriptableDataSourceEditor.AvailableQueries" ) ) );
theQueryControlsPanel.add( BorderLayout.EAST, theQueryButtonsPanel );
final JPanel queryListPanel = new JPanel( new BorderLayout() );
queryListPanel.setBorder( BorderFactory.createEmptyBorder( 0, 8, 0, 8 ) );
queryListPanel.add( BorderLayout.NORTH, theQueryControlsPanel );
queryListPanel.add( BorderLayout.CENTER, new JScrollPane( queryNameList ) );
return queryListPanel;
}
public ScriptableDataFactory performConfiguration( final ScriptableDataFactory dataFactory,
final String selectedQuery ) {
// Reset the confirmed / cancel flag
// Initialize the internal storage
queries = new TreeMap<String, DataSetQuery>();
// Load the current configuration
if ( dataFactory != null ) {
languageField.setSelectedValue( dataFactory.getLanguage(), true );
final String[] queryNames = dataFactory.getQueryNames();
for ( int i = 0; i < queryNames.length; i++ ) {
final String queryName = queryNames[ i ];
final String query = dataFactory.getQuery( queryName );
queries.put( queryName, new DataSetQuery( queryName, query ) );
}
initScriptTextArea.setText( dataFactory.getScript() );
shutdownScriptTextArea.setText( dataFactory.getShutdownScript() );
}
// Prepare the data and the enable the proper buttons
updateComponents();
updateQueryList();
setSelectedQuery( selectedQuery );
// Enable the dialog
if ( !performEdit() ) {
return null;
}
return produceFactory();
}
private ScriptableDataFactory produceFactory() {
final ScriptableDataFactory returnDataFactory = new ScriptableDataFactory();
returnDataFactory.setLanguage( (String) languageField.getSelectedValue() );
if ( StringUtils.isEmpty( initScriptTextArea.getText() ) ) {
returnDataFactory.setScript( null );
} else {
returnDataFactory.setScript( initScriptTextArea.getText() );
}
if ( StringUtils.isEmpty( shutdownScriptTextArea.getText() ) ) {
returnDataFactory.setShutdownScript( null );
} else {
returnDataFactory.setShutdownScript( shutdownScriptTextArea.getText() );
}
final DataSetQuery[] queries = this.queries.values().toArray( new DataSetQuery[ this.queries.size() ] );
for ( int i = 0; i < queries.length; i++ ) {
final DataSetQuery query = queries[ i ];
returnDataFactory.setQuery( query.getQueryName(), query.getQuery() );
}
return returnDataFactory;
}
protected void updateQueryList() {
queryNameList.removeAll();
queryNameList.setListData( queries.values().toArray( new DataSetQuery[ queries.size() ] ) );
}
private void setSelectedQuery( final String aQuery ) {
final ListModel theModel = queryNameList.getModel();
for ( int i = 0; i < theModel.getSize(); i++ ) {
final DataSetQuery theDataSet = (DataSetQuery) theModel.getElementAt( i );
if ( theDataSet.getQueryName().equals( aQuery ) ) {
queryNameList.setSelectedValue( theDataSet, true );
break;
}
}
}
protected void updateComponents() {
final boolean querySelected = queryNameList.getSelectedIndex() != -1;
final boolean hasQueries = queryNameList.getModel().getSize() > 0;
queryNameTextField.setEnabled( querySelected );
queryTextArea.setEnabled( querySelected );
getConfirmAction().setEnabled( hasQueries && languageField.getSelectedIndex() != -1 );
queryTextArea.setSyntaxEditingStyle( mapLanguageToSyntaxHighlighting( (String) languageField.getSelectedValue() ) );
initScriptTextArea
.setSyntaxEditingStyle( mapLanguageToSyntaxHighlighting( (String) languageField.getSelectedValue() ) );
shutdownScriptTextArea
.setSyntaxEditingStyle( mapLanguageToSyntaxHighlighting( (String) languageField.getSelectedValue() ) );
previewAction.setEnabled( querySelected );
}
private String mapLanguageToSyntaxHighlighting( final String language ) {
if ( "beanshell".equals( language ) ) {
return SyntaxConstants.SYNTAX_STYLE_JAVA;
}
if ( "groovy".equals( language ) ) {
return SyntaxConstants.SYNTAX_STYLE_GROOVY;
}
if ( "javascript".equals( language ) ) {
return SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT;
}
if ( "jython".equals( language ) ) {
return SyntaxConstants.SYNTAX_STYLE_PYTHON;
}
if ( "xslt".equals( language ) ) {
return SyntaxConstants.SYNTAX_STYLE_XML;
}
return SyntaxConstants.SYNTAX_STYLE_NONE;
}
}