/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * JDBCResultSetEditor.java * Created: Apr. 8 / 2003 * By: David Mosimann */ package org.openquark.gems.client.valueentry; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Point; import java.awt.dnd.DragGestureEvent; import java.awt.event.MouseEvent; import java.awt.font.FontRenderContext; import java.awt.geom.Rectangle2D; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.border.EtchedBorder; import javax.swing.table.TableColumnModel; import org.openquark.cal.compiler.QualifiedName; import org.openquark.cal.foreignsupport.module.DataGems.DatabaseException; import org.openquark.cal.foreignsupport.module.DataGems.QueryResult; import org.openquark.cal.foreignsupport.module.DataGems.RecordPlaceholder; import org.openquark.cal.module.Cal.Data.CAL_DataGems; import org.openquark.cal.services.CALWorkspace; import org.openquark.cal.valuenode.ForeignValueNode; import org.openquark.cal.valuenode.ValueNode; import org.openquark.cal.valuenode.ValueNodeBuilderHelper; /** * A ValueEditor customized for a data inspector. It will display lists and lists of tuples. */ public class JDBCResultSetEditor extends ValueEditor implements LazyLoadingTableRowProvider { private static final long serialVersionUID = 8182700672556039687L; /** * This interface defines what drag operations are supported by the * <code>JDBCResultSetEditor</code>. Implementors of this interface will be * called when a certain drag operation is initiated by the user. */ public interface JDBCResultSetDragPointHandler extends ValueEditorDragPointHandler { /** * This method will be called when the user attempts to drag a list of columns * from the JDBC ResultSet set table. By default, this method does nothing * and returns <code>false</code>. Subclasses are encouraged to override * this method to define their own dragging behaviour. * @param dge * @param parentEditor * @param columnModel * @return boolean */ boolean dragColumns( DragGestureEvent dge, JDBCResultSetEditor parentEditor, TableColumnModel columnModel); } /** * A custom value editor provider for the JDBCResultSetEditor. */ public static class JDBCResultSetEditorProvider extends ValueEditorProvider<JDBCResultSetEditor> { public JDBCResultSetEditorProvider(ValueEditorManager valueEditorManager) { super(valueEditorManager); } /** * {@inheritDoc} */ @Override public boolean canHandleValue(ValueNode valueNode, SupportInfo providerSupportInfo) { return JDBCResultSetValueNodeAdapter.canHandleValue(valueNode) // || JDBCRecordListValueNodeAdapter.canHandleValue(valueNode) ; } /** * @see org.openquark.gems.client.valueentry.ValueEditorProvider#getEditorInstance(ValueEditorHierarchyManager, ValueNode) */ @Override public JDBCResultSetEditor getEditorInstance(ValueEditorHierarchyManager valueEditorHierarchyManager, ValueNode valueNode) { JDBCResultSetEditor editor = new JDBCResultSetEditor(valueEditorHierarchyManager, null, getPerspective().getWorkspace()); editor.setOwnerValueNode(valueNode); return editor; } /* (non-Javadoc) * @see org.openquark.gems.client.valueentry.ValueEditorProvider#getEditorInstance(org.openquark.gems.client.valueentry.ValueEditorHierarchyManager, org.openquark.cal.valuenode.ValueNodeBuilderHelper, org.openquark.gems.client.valueentry.ValueEditorDragManager, org.openquark.cal.valuenode.ValueNode) */ @Override public JDBCResultSetEditor getEditorInstance( ValueEditorHierarchyManager valueEditorHierarchyManager, ValueNodeBuilderHelper valueNodeBuilderHelper, ValueEditorDragManager dragManager, ValueNode valueNode) { JDBCResultSetEditor editor = new JDBCResultSetEditor( valueEditorHierarchyManager, getJDBCResultSetDragPointHandler(dragManager), getPerspective().getWorkspace()); editor.setOwnerValueNode(valueNode); return editor; } /** * {@inheritDoc} */ @Override public boolean usableForOutput() { return true; } /** * A convenient method for casting the drag point handler to the type that is * suitable for the <code>JDBCResultSetEditor</code> to use. If such conversion is not * possible, then this method should return <code>null</code>. * @param dragManager * @return JDBCResultSetDragPointHandler */ private JDBCResultSetDragPointHandler getJDBCResultSetDragPointHandler(ValueEditorDragManager dragManager) { ValueEditorDragPointHandler handler = getDragPointHandler(dragManager); if (handler instanceof JDBCResultSetDragPointHandler) { return (JDBCResultSetDragPointHandler) handler; } return null; } } /** * A simple implementation of the JDBCQueryResultAdapter interface that is * able to adapt to a CAL value of type "DataGems.ResultSet". */ public static class JDBCResultSetValueNodeAdapter implements JDBCQueryResultAdapter { /** * CAL type used for this adapter. */ private static final QualifiedName JDBC_RESULT_SET_NAME = CAL_DataGems.TypeConstructors.ResultSet; private final QueryResult resultSet; public JDBCResultSetValueNodeAdapter(ValueNode valueNode) { resultSet = (QueryResult) valueNode.getValue(); } /** * @see org.openquark.gems.client.valueentry.JDBCQueryResultAdapter#getResultSet() */ public QueryResult getResultSet() { return resultSet; } /** * @see org.openquark.gems.client.valueentry.JDBCQueryResultAdapter#getRow(int) */ public RecordPlaceholder getRow(int index) throws DatabaseException { return resultSet.resultGetRecord(index+1); } /** * @see org.openquark.gems.client.valueentry.JDBCQueryResultAdapter#hasRow(int) */ public boolean hasRow(int index) { return resultSet.recordAt(index+1); } /** * Convenient method for testing if the given expression is of type * "DataGems.ResultSet". * @param valueNode * @return boolean */ public static boolean canHandleValue(ValueNode valueNode) { if (valueNode instanceof ForeignValueNode) { return valueNode.getTypeExpr().hasRootTypeConstructor(JDBC_RESULT_SET_NAME); } return false; } } // /** // * A slighly more complex implementation of the JDBCQueryResultAdapter interface // * that is able to adapt to a CAL value of type "[DataGems.DatabaseRecord]". // */ // public static class JDBCRecordListValueNodeAdapter implements JDBCQueryResultAdapter { // // /** // * CAL type used for this adapter. // */ // private static final QualifiedName JDBC_RECORD_NAME = QualifiedName.make("DataGems", "DatabaseRecord"); // // private QueryResult resultSet; // private ListValueNode listValueNode; // // public JDBCRecordListValueNodeAdapter(ValueNode valueNode) { // listValueNode = (ListValueNode) valueNode; // if (hasRow(0)) { // resultSet = getRow(0).getRecordSet(); // } // } // // /* (non-Javadoc) // * @see org.openquark.gems.client.valueentry.JDBCQueryResultAdapter#getResultSet() // */ // public QueryResult getResultSet() { // return resultSet; // } // // /* (non-Javadoc) // * @see org.openquark.gems.client.valueentry.JDBCQueryResultAdapter#getRow(int) // */ // public RecordPlaceholder getRow(int index) { // ValueNode node = listValueNode.getValueAt(index); // return (RecordPlaceholder) node.getValue(); // } // // /* (non-Javadoc) // * @see org.openquark.gems.client.valueentry.JDBCQueryResultAdapter#hasRow(int) // */ // public boolean hasRow(int index) { // return index >= 0 && index < listValueNode.getNElements(); // } // // /** // * Convenient method for testing if the given expression is of type "[DataGems.DatabaseRecord]". // * @param valueNode // * @return boolean // */ // public static boolean canHandleValue(ValueNode valueNode) { // if (valueNode instanceof ListValueNode) { // TypeConsApp typeConsApp = valueNode.getTypeExpr().rootTypeConsApp(); // if (typeConsApp.getNArgs() == 1) { // return typeConsApp.getArg(0).isNonParametricType(JDBC_RECORD_NAME); // } // } // return false; // } // } /** * A simple variation of the lazy loading table that uses the string representation * of a cell's value as its tooltip text. */ private static class JDBCResultSetTable extends LazyLoadingTable { private static final long serialVersionUID = 6785306966736775696L; public JDBCResultSetTable(LazyLoadingTableRowProvider provider) { super(provider); } /* (non-Javadoc) * @see javax.swing.JComponent#getToolTipText(java.awt.event.MouseEvent) */ @Override public String getToolTipText(MouseEvent event) { Point p = event.getPoint(); int row = rowAtPoint(p); int column = columnAtPoint(p); if (row >= 0 && column >= 0) { Object value = getValueAt(row, column); return value == null ? "(no value)" : value.toString(); // TODO internationalization } else { return super.getToolTipText(event); } } } protected final LazyLoadingTable table; protected final JDBCResultSetDragPointHandler dragPointHandler; protected CALWorkspace workspace; private JScrollPane scrollPane; /** * Query result adapter used for this editor. This adapter will be refreshed * whenever the owner value node is changed. */ protected JDBCQueryResultAdapter adapter; /** * JDBCResultSetEditor constructor. Intended to be used by the value editor * provider only. * * @param valueEditorHierarchyManager * @param dragPointHandler * @param workspace */ protected JDBCResultSetEditor( ValueEditorHierarchyManager valueEditorHierarchyManager, JDBCResultSetDragPointHandler dragPointHandler, CALWorkspace workspace) { super(valueEditorHierarchyManager); this.dragPointHandler = dragPointHandler; this.workspace = workspace; // Create the JTable to show the records table = new JDBCResultSetTable(this); // Initialize the components initialize(); } /** * Do some initialization work */ private void initialize() { try { setName("JDBCResultSetEditor"); setBorder(new EtchedBorder()); setLayout(new BorderLayout()); add(getScrollPane(), BorderLayout.CENTER); validate(); setSize(getPreferredSize()); } catch (Throwable ivjExc) { handleException(ivjExc); } } /** * Returns the query result adapter used for this editor. Please note that this * value can never be null. * @return JDBCQueryResultAdapter */ public JDBCQueryResultAdapter getResultAdapter() { return adapter; } /** * Converts the width, in terms of the number of character(s), to points * based on the default font. * @param width * @return double */ public double convertWidthInCharToPoint(int width) { // TODO this method for estimating the average width of the characters // supported by the font might not be accurate, but it should be okay // for now Rectangle2D rec = getFont().getStringBounds( String.valueOf("m"), new FontRenderContext(null, true, false)); return rec.getMaxX() * width; } /** * Returns the component that should be assigned focus when the control first appears */ @Override public Component getDefaultFocusComponent() { return getScrollPane(); } /** * Return the JScrollPane1 property value. * @return JScrollPane */ private JScrollPane getScrollPane() { if (scrollPane == null) { try { scrollPane = new JScrollPane(table); scrollPane.setName("JScrollPane1"); scrollPane.setOpaque(true); scrollPane.setBorder(new EtchedBorder()); scrollPane.setBackground(new Color(204, 204, 204)); } catch (Throwable ivjExc) { handleException(ivjExc); } } return scrollPane; } /** * Called whenever the part throws an exception. * @param exception Throwable */ private void handleException(Throwable exception) { /* Uncomment the following lines to print uncaught exceptions to stdout */ System.out.println("--------- UNCAUGHT EXCEPTION ---------"); exception.printStackTrace(System.out); } /** * Sets the initial value. This is used to update the UI to reflect the current value */ @Override public void setInitialValue() { getScrollPane().getViewport().add(table); // If there is a row, set edit mode on the first cell and select first row. ListSelectionModel lsm = table.getSelectionModel(); if (table.getRowCount() > 0 && lsm.isSelectionEmpty()) { lsm.setSelectionInterval(0, 0); } // Ensure that the containers update correctly to reflect the newly added table component doLayout(); } /** * Sets the ValueNode for this editor. The editor will be reinitialized with the new value * @param newValueNode */ @Override public void setOwnerValueNode(ValueNode newValueNode) { // Set the new value node super.setOwnerValueNode(newValueNode); // pick the appropriate adapter for the value node and fill the table with // new information if (JDBCResultSetValueNodeAdapter.canHandleValue(newValueNode)) { adapter = new JDBCResultSetValueNodeAdapter(newValueNode); initializeTable(); } // else if (JDBCRecordListValueNodeAdapter.canHandleValue(newValueNode)) { // adapter = new JDBCRecordListValueNodeAdapter(newValueNode); // initializeTable(); // } } /* (non-Javadoc) * @see org.openquark.gems.client.valueentry.LazyLoadingTableRowProvider#hasRow(int) */ public boolean hasRow(int row) { return adapter.hasRow(row); } /* (non-Javadoc) * @see org.openquark.gems.client.valueentry.LazyLoadingTableRowProvider#loadRow(int) */ public Object[] loadRow(int row) { if (adapter.hasRow(row)) { try { RecordPlaceholder record = adapter.getRow(row); int size = adapter.getResultSet().getColumnCount(); Object data[] = new Object[size]; for (int i = 0; i < size; ++i) { data[i] = record.extractObject(i+1); } return data; } catch (DatabaseException sqle) { // FIXME Log error } } return null; } private void initializeTable() { // Some variables we want to set and use inside the try/catch blocks LazyLoadingTableModel model = new LazyLoadingTableModel(); TableColumnModel columnModel = table.getColumnModel(); table.setTableHeader(new JDBCResultSetTableHeader(columnModel, this, dragPointHandler)); QueryResult recordSet; int nColumns = 0; boolean failed = false; try { // Get some information from the Record Set recordSet = adapter.getResultSet(); if (recordSet != null) { // Set the appropriate columns into the table (note that this is 1-based not 0-based) nColumns = recordSet.getColumnCount(); for (int i = 1; i <= nColumns; ++i) { model.addColumn(recordSet.getColumnName(i)); } } else { // cannot get the result set object: no column list failed = true; } } catch (DatabaseException e) { // FIXME Log this error failed = true; } finally { // If we fail to setup the headers then we're hooped. Set an error message and return. if (failed) { model = new LazyLoadingTableModel(); // TODO: localize the string model.addColumn("Failed to Browse Data"); table.setModel(model); return; } } // We have finished building up the model so we need to set it into the table model.setGloballyEditable(false); table.setModel(model); // Resize the columns to make the UI look nicer. table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); table.doLayout(); } /** * The JDBCResultSetEditor can only be used for output. The values can not be edited * @return boolean */ @Override public boolean isEditable() { return false; } }