org.graalvm.visualvm.modules.mbeans.XMBeanAttributes Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org-graalvm-visualvm-modules-mbeans Show documentation
Show all versions of org-graalvm-visualvm-modules-mbeans Show documentation
The MBeans plugin integrates JConsole's MBeans tab functionality into VisualVM.
/*
* Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.graalvm.visualvm.modules.mbeans;
import org.graalvm.visualvm.tools.jmx.CachedMBeanServerConnection;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.*;
import java.awt.Dimension;
import java.io.IOException;
import java.util.*;
import java.lang.reflect.Array;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.*;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.TabularData;
/*IMPORTANT :
There is a deadlock issue there if we don't synchronize well loadAttributes,
refresh attributes and empty table methods since a UI thread can call
loadAttributes and at the same time a JMX notification can raise an
emptyTable. Since there are synchronization in the JMX world it's
COMPULSORY to not call the JMX world in synchronized blocks */
class XMBeanAttributes extends XTable {
private static final Logger LOGGER = Logger.getLogger(XMBeanAttributes.class.getName());
private final static String[] columnNames =
{Resources.getText("LBL_Name"), // NOI18N
Resources.getText("LBL_Value")}; // NOI18N
private XMBean mbean;
private MBeanInfo mbeanInfo;
private MBeanAttributeInfo[] attributesInfo;
private HashMap attributes;
private HashMap unavailableAttributes;
private HashMap viewableAttributes;
private WeakHashMap> viewersCache =
new WeakHashMap>();
private final TableModelListener attributesListener;
private MBeansTab mbeansTab;
private TableCellEditor valueCellEditor = new ValueCellEditor();
private int rowMinHeight = -1;
private AttributesMouseListener mouseListener = new AttributesMouseListener();
private static TableCellEditor editor =
new Utils.ReadOnlyTableCellEditor(new JTextField());
public XMBeanAttributes(MBeansTab mbeansTab) {
super();
this.mbeansTab = mbeansTab;
((DefaultTableModel)getModel()).setColumnIdentifiers(columnNames);
attributesListener = new AttributesListener(this);
getModel().addTableModelListener(attributesListener);
getColumnModel().getColumn(NAME_COLUMN).setPreferredWidth(40);
addMouseListener(mouseListener);
getTableHeader().setReorderingAllowed(false);
setColumnEditors();
addKeyListener(new Utils.CopyKeyAdapter());
}
@Override
public synchronized Component prepareRenderer(TableCellRenderer renderer,
int row, int column) {
//In case we have a repaint thread that is in the process of
//repainting an obsolete table, just ignore the call.
//It can happen when MBean selection is switched at a very quick rate
if(row >= getRowCount())
return null;
else
return super.prepareRenderer(renderer, row, column);
}
void updateRowHeight(Object obj, int row) {
ZoomedCell cell = null;
if(obj instanceof ZoomedCell) {
cell = (ZoomedCell) obj;
if(cell.isInited())
setRowHeight(row, cell.getHeight());
else
if(rowMinHeight != - 1)
setRowHeight(row, rowMinHeight);
} else
if(rowMinHeight != - 1)
setRowHeight(row, rowMinHeight);
}
@Override
public synchronized TableCellRenderer getCellRenderer(int row,
int column) {
//In case we have a repaint thread that is in the process of
//repainting an obsolete table, just ignore the call.
//It can happen when MBean selection is switched at a very quick rate
if (row >= getRowCount()) {
return null;
} else {
if (column == VALUE_COLUMN) {
Object obj = getModel().getValueAt(row, column);
if (obj instanceof ZoomedCell) {
ZoomedCell cell = (ZoomedCell) obj;
if (cell.isInited()) {
DefaultTableCellRenderer renderer =
(DefaultTableCellRenderer) cell.getRenderer();
renderer.setToolTipText(getToolTip(row,column));
return renderer;
}
}
}
DefaultTableCellRenderer renderer = (DefaultTableCellRenderer)
super.getCellRenderer(row, column);
if (!isCellError(row, column)) {
if (!(isColumnEditable(column) && isWritable(row) &&
Utils.isEditableType(getClassName(row)))) {
renderer.setForeground(getDefaultColor());
}
}
return renderer;
}
}
private void setColumnEditors() {
TableColumnModel tcm = getColumnModel();
for (int i = 0; i < columnNames.length; i++) {
TableColumn tc = tcm.getColumn(i);
if (isColumnEditable(i)) {
tc.setCellEditor(valueCellEditor);
} else {
tc.setCellEditor(editor);
}
}
}
public void cancelCellEditing() {
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("Cancel Editing Row: "+getEditingRow());
}
final TableCellEditor tableCellEditor = getCellEditor();
if (tableCellEditor != null) {
tableCellEditor.cancelCellEditing();
}
}
public void stopCellEditing() {
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("Stop Editing Row: "+getEditingRow());
}
final TableCellEditor tableCellEditor = getCellEditor();
if (tableCellEditor != null) {
tableCellEditor.stopCellEditing();
}
}
@Override
public final boolean editCellAt(final int row, final int column, EventObject e) {
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("editCellAt(row="+row+", col="+column+
", e="+e+")");
}
boolean retVal = super.editCellAt(row, column, e);
if (retVal) {
final TableCellEditor tableCellEditor =
getColumnModel().getColumn(column).getCellEditor();
if (tableCellEditor == valueCellEditor) {
((JComponent) tableCellEditor).requestFocus();
}
}
return retVal;
}
@Override
public boolean isCellEditable(int row, int col) {
// All the cells in non-editable columns are editable
if (!isColumnEditable(col)) {
return true;
}
// Maximized zoomed cells are editable
Object obj = getModel().getValueAt(row, col);
if (obj instanceof ZoomedCell) {
ZoomedCell cell = (ZoomedCell) obj;
return cell.isMaximized();
}
return true;
}
@Override
public void setValueAt(Object value, int row, int column) {
if (!isCellError(row, column) && isColumnEditable(column) &&
isWritable(row) && Utils.isEditableType(getClassName(row))) {
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("setValueAt(row="+row+", column="+column+
"): "+getValueName(row)+"="+value);
}
super.setValueAt(value, row, column);
}
}
//Table methods
public boolean isTableEditable() {
return true;
}
public void setTableValue(Object value, int row) {
}
public boolean isColumnEditable(int column) {
if (column < getColumnCount()) {
return getColumnName(column).equals(Resources.getText("LBL_Value")); // NOI18N
}
else {
return false;
}
}
public String getClassName(int row) {
int index = convertRowToIndex(row);
if (index != -1) {
return attributesInfo[index].getType();
}
else {
return null;
}
}
public String getValueName(int row) {
int index = convertRowToIndex(row);
if (index != -1) {
return attributesInfo[index].getName();
}
else {
return null;
}
}
public Object getValue(int row) {
final Object val = ((DefaultTableModel) getModel())
.getValueAt(row, VALUE_COLUMN);
return val;
}
//tool tip only for editable column
@Override
public String getToolTip(int row, int column) {
if (isCellError(row, column)) {
return (String) unavailableAttributes.get(getValueName(row));
}
if (isColumnEditable(column)) {
Object value = getValue(row);
String tip = null;
if (value != null) {
tip = value.toString();
if(isAttributeViewable(row, VALUE_COLUMN))
tip = Resources.getText("LBL_DoubleClickToExpandCollapse")+ // NOI18N
". " + tip; // NOI18N
}
return tip;
}
if(column == NAME_COLUMN) {
int index = convertRowToIndex(row);
if (index != -1) {
return attributesInfo[index].getDescription();
}
}
return null;
}
public synchronized boolean isWritable(int row) {
int index = convertRowToIndex(row);
if (index != -1) {
return (attributesInfo[index].isWritable());
}
else {
return false;
}
}
/**
* Override JTable method in order to make any call to this method
* atomic with TableModel elements.
*/
@Override
public synchronized int getRowCount() {
return super.getRowCount();
}
public synchronized boolean isReadable(int row) {
int index = convertRowToIndex(row);
if (index != -1) {
return (attributesInfo[index].isReadable());
}
else {
return false;
}
}
public synchronized boolean isCellError(int row, int col) {
return (isColumnEditable(col) &&
(unavailableAttributes.containsKey(getValueName(row))));
}
public synchronized boolean isAttributeViewable(int row, int col) {
boolean isViewable = false;
if(col == VALUE_COLUMN) {
Object obj = getModel().getValueAt(row, col);
if(obj instanceof ZoomedCell)
isViewable = true;
}
return isViewable;
}
// Call this in EDT
public void loadAttributes(final XMBean mbean, final MBeanInfo mbeanInfo) {
final SwingWorker load =
new SwingWorker() {
@Override
protected Runnable doInBackground() throws Exception {
return doLoadAttributes(mbean,mbeanInfo);
}
@Override
protected void done() {
try {
final Runnable updateUI = get();
if (updateUI != null) updateUI.run();
} catch (RuntimeException x) {
throw x;
} catch (ExecutionException x) {
LOGGER.log(Level.FINE,
"Exception raised while loading attributes",
x.getCause());
} catch (InterruptedException x) {
LOGGER.log(Level.FINE,
"Interrupted while loading attributes",x);
}
}
};
mbeansTab.getRequestProcessor().post(load);
}
// Don't call this in EDT, but execute returned Runnable inside
// EDT - typically in the done() method of a SwingWorker
// This method can return null.
private Runnable doLoadAttributes(final XMBean mbean, MBeanInfo infoOrNull)
throws JMException, IOException {
// To avoid deadlock with events coming from the JMX side,
// we retrieve all JMX stuff in a non synchronized block.
if(mbean == null) return null;
final MBeanInfo curMBeanInfo =
(infoOrNull==null)?mbean.getMBeanInfo():infoOrNull;
final MBeanAttributeInfo[] attrsInfo = curMBeanInfo.getAttributes();
final HashMap attrs =
new HashMap(attrsInfo.length);
final HashMap unavailableAttrs =
new HashMap(attrsInfo.length);
final HashMap viewableAttrs =
new HashMap(attrsInfo.length);
AttributeList list = null;
try {
list = mbean.getAttributes(attrsInfo);
}catch(Exception e) {
list = new AttributeList();
//Can't load all attributes, do it one after each other.
for(int i = 0; i < attrsInfo.length; i++) {
String name = null;
try {
name = attrsInfo[i].getName();
Object value =
mbean.getMBeanServerConnection().
getAttribute(mbean.getObjectName(), name);
list.add(new Attribute(name, value));
}catch(Exception ex) {
if(attrsInfo[i].isReadable()) {
unavailableAttrs.put(name,
Utils.getActualException(ex).toString());
}
}
}
}
try {
int att_length = list.size();
for (int i=0;i stopCellEditing -> setValueAt -> tableChanged
// -> refreshAttributes(false)
//
// Can be called in EDT - as long as the implementation of
// mbeansTab.getCachedMBeanServerConnection() and mbsc.flush() doesn't
// change
//
private void refreshAttributes(final boolean stopCellEditing) {
SwingWorker sw = new SwingWorker() {
@Override
protected Void doInBackground() throws Exception {
CachedMBeanServerConnection mbsc =
mbeansTab.getCachedMBeanServerConnection();
mbsc.flush();
return null;
}
@Override
protected void done() {
try {
get();
if (stopCellEditing) stopCellEditing();
loadAttributes(mbean, mbeanInfo);
} catch (Exception x) {
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.log(Level.FINER,
"Unexpected exception while loading attributes",// No I18N
x);
}
}
}
};
mbeansTab.getRequestProcessor().post(sw);
}
// We need to call stop editing here - otherwise edits are lost
// when resizing the table.
//
@Override
public void columnMarginChanged(ChangeEvent e) {
if (isEditing()) stopCellEditing();
super.columnMarginChanged(e);
}
// We need to call stop editing here - otherwise the edited value
// is transferred to the wrong row...
//
@Override
void sortRequested(int column) {
if (isEditing()) stopCellEditing();
super.sortRequested(column);
}
@Override
public synchronized void emptyTable() {
emptyTable((DefaultTableModel)getModel());
}
// Call this in synchronized block.
private void emptyTable(DefaultTableModel model) {
model.removeTableModelListener(attributesListener);
super.emptyTable();
}
private boolean isViewable(Attribute attribute) {
Object data = attribute.getValue();
return XDataViewer.isViewableValue(data);
}
synchronized void removeAttributes() {
if (attributes != null) {
attributes.clear();
}
if (unavailableAttributes != null) {
unavailableAttributes.clear();
}
if (viewableAttributes != null) {
viewableAttributes.clear();
}
mbean = null;
}
private ZoomedCell getZoomedCell(XMBean mbean, String attribute, Object value) {
synchronized (viewersCache) {
HashMap viewers;
if (viewersCache.containsKey(mbean)) {
viewers = viewersCache.get(mbean);
} else {
viewers = new HashMap();
}
ZoomedCell cell;
if (viewers.containsKey(attribute)) {
cell = viewers.get(attribute);
cell.setValue(value);
if (cell.isMaximized() && cell.getType() != XDataViewer.NUMERIC) {
// Plotters are the only viewers with auto update capabilities.
// Other viewers need to be updated manually.
Component comp =
mbeansTab.getDataViewer().createAttributeViewer(
value, mbean, attribute, XMBeanAttributes.this);
cell.init(cell.getMinRenderer(), comp, cell.getMinHeight());
mbeansTab.getDataViewer().registerForMouseEvent(comp, mouseListener);
}
} else {
cell = new ZoomedCell(value);
viewers.put(attribute, cell);
}
viewersCache.put(mbean, viewers);
return cell;
}
}
//will be called in a synchronzed block
protected void addTableData(DefaultTableModel tableModel,
XMBean mbean,
MBeanAttributeInfo[] attributesInfo,
HashMap attributes,
HashMap unavailableAttributes,
HashMap viewableAttributes) {
Object rowData[] = new Object[2];
int col1Width = 0;
int col2Width = 0;
for (int i = 0; i < attributesInfo.length; i++) {
rowData[0] = (attributesInfo[i].getName());
if (unavailableAttributes.containsKey(rowData[0])) {
rowData[1] = Resources.getText("LBL_Unavailable"); // NOI18N
} else if (viewableAttributes.containsKey(rowData[0])) {
rowData[1] = viewableAttributes.get(rowData[0]);
if (!attributesInfo[i].isWritable() ||
!Utils.isEditableType(attributesInfo[i].getType())) {
rowData[1] = getZoomedCell(mbean, (String) rowData[0], rowData[1]);
}
} else {
rowData[1] = attributes.get(rowData[0]);
}
tableModel.addRow(rowData);
//Update column width
//
String str = null;
if(rowData[0] != null) {
str = rowData[0].toString();
if(str.length() > col1Width)
col1Width = str.length();
}
if(rowData[1] != null) {
str = rowData[1].toString();
if(str.length() > col2Width)
col2Width = str.length();
}
}
updateColumnWidth(col1Width, col2Width);
}
private void updateColumnWidth(int col1Width, int col2Width) {
TableColumnModel colModel = getColumnModel();
//Get the column at index pColumn, and set its preferred width.
col1Width = col1Width * 7;
col2Width = col2Width * 7;
if(col1Width + col2Width <
(int) getPreferredScrollableViewportSize().getWidth())
col2Width = (int) getPreferredScrollableViewportSize().getWidth()
- col1Width;
colModel.getColumn(NAME_COLUMN).setPreferredWidth(50);
}
class AttributesMouseListener extends MouseAdapter {
@Override
public void mousePressed(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON1) {
if(e.getClickCount() >= 2) {
int row = XMBeanAttributes.this.getSelectedRow();
int col = XMBeanAttributes.this.getSelectedColumn();
if(col != VALUE_COLUMN) return;
if(col == -1 || row == -1) return;
XMBeanAttributes.this.updateZoomedCell(row, col);
}
}
}
}
@SuppressWarnings("serial")
class ValueCellEditor extends XTextFieldEditor {
// implements javax.swing.table.TableCellEditor
@Override
public Component getTableCellEditorComponent(JTable table,
Object value,
boolean isSelected,
int row,
int column) {
Object val = value;
if(column == VALUE_COLUMN) {
Object obj = getModel().getValueAt(row,
column);
if(obj instanceof ZoomedCell) {
ZoomedCell cell = (ZoomedCell) obj;
if(cell.getRenderer() instanceof MaximizedCellRenderer) {
MaximizedCellRenderer zr =
(MaximizedCellRenderer) cell.getRenderer();
return zr.getComponent();
}
} else {
Component comp = super.getTableCellEditorComponent(
table, val, isSelected, row, column);
if (isCellError(row, column) ||
!isWritable(row) ||
!Utils.isEditableType(getClassName(row))) {
textField.setEditable(false);
}
return comp;
}
}
return super.getTableCellEditorComponent(table,
val,
isSelected,
row,
column);
}
@Override
public boolean stopCellEditing() {
int editingRow = getEditingRow();
int editingColumn = getEditingColumn();
if (editingColumn == VALUE_COLUMN) {
Object obj = getModel().getValueAt(editingRow, editingColumn);
if (obj instanceof ZoomedCell) {
ZoomedCell cell = (ZoomedCell) obj;
if (cell.isMaximized()) {
this.cancelCellEditing();
return true;
}
}
}
return super.stopCellEditing();
}
}
@SuppressWarnings("serial")
class MaximizedCellRenderer extends DefaultTableCellRenderer {
Component comp;
MaximizedCellRenderer(Component comp) {
this.comp = comp;
Dimension d = comp.getPreferredSize();
if (d.getHeight() > 220) {
comp.setPreferredSize(new Dimension((int) d.getWidth(), 220));
}
}
@Override
public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column) {
return comp;
}
public Component getComponent() {
return comp;
}
}
class ZoomedCell {
TableCellRenderer minRenderer;
MaximizedCellRenderer maxRenderer;
int minHeight;
boolean minimized = true;
boolean init = false;
int type;
Object value;
ZoomedCell(Object value) {
type = XDataViewer.getViewerType(value);
this.value = value;
}
boolean isInited() {
return init;
}
Object getValue() {
return value;
}
void setValue(Object value) {
this.value = value;
}
void init(TableCellRenderer minRenderer,
Component maxComponent,
int minHeight) {
this.minRenderer = minRenderer;
this.maxRenderer = new MaximizedCellRenderer(maxComponent);
this.minHeight = minHeight;
init = true;
}
int getType() {
return type;
}
void reset() {
init = false;
minimized = true;
}
void switchState() {
minimized = !minimized;
}
boolean isMaximized() {
return !minimized;
}
void minimize() {
minimized = true;
}
void maximize() {
minimized = false;
}
int getHeight() {
if(minimized) return minHeight;
else
return (int) maxRenderer.getComponent().
getPreferredSize().getHeight() ;
}
int getMinHeight() {
return minHeight;
}
@Override
public String toString() {
if(value == null) return null;
if(value.getClass().isArray()) {
String name =
Utils.getArrayClassName(value.getClass().getName());
int length = Array.getLength(value);
return name + "[" + length +"]"; // NOI18N
}
if(value instanceof CompositeData ||
value instanceof TabularData)
return value.getClass().getName();
return value.toString();
}
TableCellRenderer getRenderer() {
if(minimized) return minRenderer;
else return maxRenderer;
}
TableCellRenderer getMinRenderer() {
return minRenderer;
}
}
class AttributesListener implements TableModelListener {
private Component component;
public AttributesListener(Component component) {
this.component = component;
}
// Call this in EDT
public void tableChanged(final TableModelEvent e) {
// only post changes to the draggable column
if (isColumnEditable(e.getColumn())) {
final TableModel model = (TableModel)e.getSource();
Object tableValue = model.getValueAt(e.getFirstRow(),
e.getColumn());
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("tableChanged: firstRow="+e.getFirstRow()+
", lastRow="+e.getLastRow()+", column="+e.getColumn()+
", value="+tableValue);
}
// if it's a String, try construct new value
// using the defined type.
if (tableValue instanceof String) {
try {
tableValue =
Utils.createObjectFromString(getClassName(e.getFirstRow()), // type
(String)tableValue);// value
} catch (Throwable ex) {
popupAndLog(ex,"tableChanged",
"LBL_ProblemSettingAttribute");
}
}
final String attributeName = getValueName(e.getFirstRow());
final Attribute attribute =
new Attribute(attributeName,tableValue);
setAttribute(attribute, "tableChanged");
}
}
// Call this in EDT
private void setAttribute(final Attribute attribute, final String method) {
final SwingWorker setAttribute =
new SwingWorker() {
@Override
protected Void doInBackground() throws Exception {
try {
mbean.setAttribute(attribute);
} catch (Throwable ex) {
popupAndLog(ex,method,"LBL_ProblemSettingAttribute");
}
return null;
}
@Override
protected void done() {
try {
get();
} catch (Exception x) {
// XX should not happen
// XXX log this
}
refreshAttributes(false);
}
};
mbeansTab.getRequestProcessor().post(setAttribute);
}
// Call this outside EDT
private void popupAndLog(Throwable ex, String method, String key) {
ex = Utils.getActualException(ex);
LOGGER.throwing(XMBeanAttributes.class.getName(), method, ex); // NOI18N
String message = (ex.getMessage() != null) ? ex.getMessage()
: ex.toString();
EventQueue.invokeLater(
new ThreadDialog(component,
message+"\n", // NOI18N
Resources.getText(key), // NOI18N
JOptionPane.ERROR_MESSAGE));
}
}
}