/*
* 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) 2009 Pentaho Corporation. All rights reserved.
*/
package org.pentaho.reporting.ui.datasources.scriptable;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.FlowLayout;
import java.awt.Frame;
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;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
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 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.AbstractReportDefinition;
import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot;
import org.pentaho.reporting.engine.classic.core.DefaultReportEnvironment;
import org.pentaho.reporting.engine.classic.core.MasterReport;
import org.pentaho.reporting.engine.classic.core.ReportDataFactoryException;
import org.pentaho.reporting.engine.classic.core.ReportEnvironment;
import org.pentaho.reporting.engine.classic.core.designtime.DesignTimeContext;
import org.pentaho.reporting.engine.classic.core.designtime.DesignTimeUtil;
import org.pentaho.reporting.engine.classic.core.modules.gui.commonswing.ExceptionDialog;
import org.pentaho.reporting.engine.classic.core.modules.gui.commonswing.SwingUtil;
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.config.Configuration;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.libraries.designtime.swing.BorderlessButton;
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 org.pentaho.reporting.libraries.resourceloader.ResourceKey;
/**
* @author David Kincade
*/
public class ScriptableDataSourceEditor extends JDialog
{
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 CancelAction extends AbstractAction
{
private CancelAction()
{
putValue(Action.NAME, Messages.getString("ScriptableDataSourceEditor.Cancel.Name"));
}
public void actionPerformed(final ActionEvent e)
{
dispose();
}
}
private class OKAction extends AbstractAction
{
private OKAction()
{
putValue(Action.NAME, Messages.getString("ScriptableDataSourceEditor.OK.Name"));
}
public void actionPerformed(final ActionEvent e)
{
confirmed = true;
dispose();
}
}
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();
final AbstractReportDefinition report = designTimeContext.getReport();
final MasterReport masterReport = DesignTimeUtil.getMasterReport(report);
final Configuration configuration;
final ResourceKey contentBase;
final ReportEnvironment reportEnvironment;
if (masterReport == null)
{
contentBase = null;
configuration = ClassicEngineBoot.getInstance().getGlobalConfig();
reportEnvironment = new DefaultReportEnvironment(configuration);
}
else
{
contentBase = masterReport.getContentBase();
configuration = masterReport.getConfiguration();
reportEnvironment = masterReport.getReportEnvironment();
}
dataFactory.initialize(configuration,
report.getResourceManager(), contentBase, MasterReport.computeAndInitResourceBundleFactory
(report.getResourceBundleFactory(), reportEnvironment));
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
{
dataFactory.open();
resultTableModel = dataFactory.queryData(query, new ReportParameterValues());
dataFactory.close();
}
catch (ReportDataFactoryException e)
{
exception = e;
}
}
}
private boolean confirmed;
private JList queryNameList;
private JTextField queryNameTextField;
private JList languageField;
private RSyntaxTextArea queryTextArea;
private Map<String, DataSetQuery> queries;
private boolean inQueryNameUpdate;
private boolean inModifyingQueryNameList;
private OKAction okAction;
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);
okAction = new OKAction();
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());
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);
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 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, queryContentPanel);
contentPanel.add(BorderLayout.SOUTH, previewButtonPanel);
final JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 5));
buttonPanel.add(new JButton(okAction));
buttonPanel.add(new JButton(new CancelAction()));
setLayout(new BorderLayout());
add(BorderLayout.CENTER, contentPanel);
add(BorderLayout.SOUTH, buttonPanel);
pack();
SwingUtil.centerDialogInParent(this);
}
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
confirmed = false;
// 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));
}
}
// Prepare the data and the enable the proper buttons
updateComponents();
updateQueryList();
setSelectedQuery(selectedQuery);
// Enable the dialog
setVisible(true);
if (!confirmed)
{
return null;
}
return produceFactory();
}
private ScriptableDataFactory produceFactory()
{
final ScriptableDataFactory returnDataFactory = new ScriptableDataFactory();
returnDataFactory.setLanguage((String) languageField.getSelectedValue());
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);
okAction.setEnabled(hasQueries && languageField.getSelectedIndex() != -1);
queryTextArea.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;
}
}