TableViewEntryPanel.java revision 998747bfaaa3c6b28bbfaf0e282e6c0ccbf46bc0
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2008-2010 Sun Microsystems, Inc.
* Portions Copyright 2014-2015 ForgeRock AS
*/
package org.opends.guitools.controlpanel.ui;
import static org.opends.messages.AdminToolMessages.*;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.swing.Box;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.tree.TreePath;
import org.opends.guitools.controlpanel.datamodel.BinaryValue;
import org.opends.guitools.controlpanel.datamodel.CustomSearchResult;
import org.opends.guitools.controlpanel.datamodel.ObjectClassValue;
import org.opends.guitools.controlpanel.datamodel.SortableTableModel;
import org.opends.guitools.controlpanel.task.OnlineUpdateException;
import org.opends.guitools.controlpanel.ui.renderer.AttributeCellEditor;
import org.opends.guitools.controlpanel.ui.renderer.LDAPEntryTableCellRenderer;
import org.opends.guitools.controlpanel.util.Utilities;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.ldap.ByteString;
import org.opends.server.types.*;
import org.opends.server.util.LDIFReader;
import org.opends.server.util.ServerConstants;
/**
* The panel displaying a table view of an LDAP entry.
*/
public class TableViewEntryPanel extends ViewEntryPanel
{
private static final long serialVersionUID = 2135331526526472175L;
private CustomSearchResult searchResult;
private LDAPEntryTableModel tableModel;
private LDAPEntryTableCellRenderer renderer;
private JTable table;
private boolean isReadOnly;
private TreePath treePath;
private JScrollPane scroll;
private AttributeCellEditor editor;
private JLabel requiredLabel;
private JCheckBox showOnlyAttrsWithValues;
/**
* Default constructor.
*
*/
public TableViewEntryPanel()
{
super();
createLayout();
}
/**
* {@inheritDoc}
*/
public Component getPreferredFocusComponent()
{
return table;
}
/**
* Creates the layout of the panel (but the contents are not populated here).
*/
private void createLayout()
{
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridwidth = 2;
gbc.fill = GridBagConstraints.NONE;
gbc.anchor = GridBagConstraints.WEST;
gbc.weightx = 1.0;
addTitlePanel(this, gbc);
gbc.gridy ++;
gbc.insets.top = 5;
gbc.gridwidth = 1;
showOnlyAttrsWithValues = Utilities.createCheckBox(
INFO_CTRL_PANEL_SHOW_ATTRS_WITH_VALUES_LABEL.get());
showOnlyAttrsWithValues.setSelected(displayOnlyWithAttrs);
showOnlyAttrsWithValues.addActionListener(new ActionListener()
{
/**
* {@inheritDoc}
*/
public void actionPerformed(ActionEvent ev)
{
updateAttributeVisibility();
displayOnlyWithAttrs = showOnlyAttrsWithValues.isSelected();
}
});
gbc.weightx = 0.0;
gbc.anchor = GridBagConstraints.WEST;
add(showOnlyAttrsWithValues, gbc);
gbc.gridx ++;
gbc.anchor = GridBagConstraints.EAST;
gbc.fill = GridBagConstraints.NONE;
requiredLabel = createRequiredLabel();
add(requiredLabel, gbc);
gbc.insets = new Insets(0, 0, 0, 0);
add(Box.createVerticalStrut(10), gbc);
showOnlyAttrsWithValues.setFont(requiredLabel.getFont());
gbc.gridy ++;
gbc.gridx = 0;
gbc.insets.top = 10;
gbc.gridwidth = 2;
tableModel = new LDAPEntryTableModel();
renderer = new LDAPEntryTableCellRenderer();
table = Utilities.createSortableTable(tableModel, renderer);
renderer.setTable(table);
editor = new AttributeCellEditor();
table.getColumnModel().getColumn(1).setCellEditor(editor);
gbc.weighty = 1.0;
gbc.fill = GridBagConstraints.BOTH;
gbc.gridy ++;
scroll = Utilities.createScrollPane(table);
add(scroll, gbc);
}
/**
* {@inheritDoc}
*/
public void update(CustomSearchResult sr, boolean isReadOnly, TreePath path)
{
boolean sameEntry = false;
if ((searchResult != null) && (sr != null))
{
sameEntry = searchResult.getDN().equals(sr.getDN());
}
searchResult = sr;
final Point p = sameEntry ? scroll.getViewport().getViewPosition() :
new Point(0, 0);
renderer.setSchema(getInfo().getServerDescriptor().getSchema());
editor.setInfo(getInfo());
requiredLabel.setVisible(!isReadOnly);
this.isReadOnly = isReadOnly;
this.treePath = path;
updateTitle(sr, path);
ignoreEntryChangeEvents = true;
tableModel.displayEntry();
Utilities.updateTableSizes(table);
Utilities.updateScrollMode(scroll, table);
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
if ((p != null) && (scroll.getViewport().contains(p)))
{
scroll.getViewport().setViewPosition(p);
}
ignoreEntryChangeEvents = false;
}
});
}
/**
* {@inheritDoc}
*/
public GenericDialog.ButtonType getButtonType()
{
return GenericDialog.ButtonType.NO_BUTTON;
}
/**
* {@inheritDoc}
*/
public Entry getEntry() throws OpenDsException
{
if (SwingUtilities.isEventDispatchThread())
{
editor.stopCellEditing();
}
else
{
try
{
SwingUtilities.invokeAndWait(new Runnable()
{
public void run()
{
editor.stopCellEditing();
}
});
}
catch (Throwable t)
{
}
}
Entry entry = null;
LDIFImportConfig ldifImportConfig = null;
try
{
String ldif = getLDIF();
ldifImportConfig = new LDIFImportConfig(new StringReader(ldif));
LDIFReader reader = new LDIFReader(ldifImportConfig);
entry = reader.readEntry(checkSchema());
addValuesInRDN(entry);
}
catch (IOException ioe)
{
throw new OnlineUpdateException(
ERR_CTRL_PANEL_ERROR_CHECKING_ENTRY.get(ioe), ioe);
}
finally
{
if (ldifImportConfig != null)
{
ldifImportConfig.close();
}
}
return entry;
}
/**
* Returns the LDIF representation of the displayed entry.
* @return the LDIF representation of the displayed entry.
*/
private String getLDIF()
{
StringBuilder sb = new StringBuilder();
sb.append("dn: ").append(getDisplayedDN());
for (int i=0; i<tableModel.getRowCount(); i++)
{
String attrName = (String)tableModel.getValueAt(i, 0);
if (!schemaReadOnlyAttributesLowerCase.contains(attrName.toLowerCase()))
{
Object value = tableModel.getValueAt(i, 1);
appendLDIFLine(sb, attrName, value);
}
}
return sb.toString();
}
/**
* {@inheritDoc}
*/
protected String getDisplayedDN()
{
StringBuilder sb = new StringBuilder();
try
{
DN oldDN = DN.valueOf(searchResult.getDN());
if (oldDN.size() > 0)
{
RDN rdn = oldDN.rdn();
List<AttributeType> attributeTypes = new ArrayList<AttributeType>();
List<String> attributeNames = new ArrayList<String>();
List<ByteString> attributeValues = new ArrayList<ByteString>();
for (int i=0; i<rdn.getNumValues(); i++)
{
String attrName = rdn.getAttributeName(i);
ByteString value = rdn.getAttributeValue(i);
Set<String> values = getDisplayedStringValues(attrName);
if (!values.contains(value.toString()))
{
if (values.size() > 0)
{
String firstNonEmpty = null;
for (String v : values)
{
v = v.trim();
if (v.length() > 0)
{
firstNonEmpty = v;
break;
}
}
if (firstNonEmpty != null)
{
AttributeType attr = rdn.getAttributeType(i);
attributeTypes.add(attr);
attributeNames.add(rdn.getAttributeName(i));
attributeValues.add(ByteString.valueOf(firstNonEmpty));
}
}
}
else
{
attributeTypes.add(rdn.getAttributeType(i));
attributeNames.add(rdn.getAttributeName(i));
attributeValues.add(value);
}
}
if (attributeTypes.size() == 0)
{
// Check the attributes in the order that we display them and use
// the first one.
Schema schema = getInfo().getServerDescriptor().getSchema();
if (schema != null)
{
for (int i=0; i<table.getRowCount(); i++)
{
String attrName = (String)table.getValueAt(i, 0);
if (isPassword(attrName) ||
attrName.equals(
ServerConstants.OBJECTCLASS_ATTRIBUTE_TYPE_NAME) ||
!table.isCellEditable(i, 1))
{
continue;
}
Object o = table.getValueAt(i, 1);
if (o instanceof String)
{
String aName =
Utilities.getAttributeNameWithoutOptions(attrName);
AttributeType attr =
schema.getAttributeType(aName.toLowerCase());
if (attr != null)
{
attributeTypes.add(attr);
attributeNames.add(attrName);
attributeValues.add(ByteString.valueOf((String) o));
}
break;
}
}
}
}
DN parent = oldDN.parent();
if (attributeTypes.size() > 0)
{
DN newDN;
RDN newRDN = new RDN(attributeTypes, attributeNames, attributeValues);
if (parent == null)
{
newDN = new DN(new RDN[]{newRDN});
}
else
{
newDN = parent.child(newRDN);
}
sb.append(newDN);
}
else
{
if (parent != null)
{
sb.append(",").append(parent);
}
}
}
}
catch (Throwable t)
{
throw new RuntimeException("Unexpected error: "+t, t);
}
return sb.toString();
}
private Set<String> getDisplayedStringValues(String attrName)
{
Set<String> values = new LinkedHashSet<String>();
for (int i=0; i<table.getRowCount(); i++)
{
if (attrName.equalsIgnoreCase((String)table.getValueAt(i, 0)))
{
Object o = table.getValueAt(i, 1);
if (o instanceof String)
{
values.add((String)o);
}
}
}
return values;
}
private void updateAttributeVisibility()
{
tableModel.updateAttributeVisibility();
}
/**
* {@inheritDoc}
*/
protected List<Object> getValues(String attrName)
{
return tableModel.getValues(attrName);
}
/**
* The table model used by the tree in the panel.
*
*/
protected class LDAPEntryTableModel extends SortableTableModel
implements Comparator<AttributeValuePair>
{
private static final long serialVersionUID = -1240282431326505113L;
private ArrayList<AttributeValuePair> dataArray =
new ArrayList<AttributeValuePair>();
private SortedSet<AttributeValuePair> allSortedValues =
new TreeSet<AttributeValuePair>(this);
Set<String> requiredAttrs = new HashSet<String>();
private final String[] COLUMN_NAMES = new String[] {
getHeader(LocalizableMessage.raw("Attribute"), 40),
getHeader(LocalizableMessage.raw("Value", 40))};
private int sortColumn;
private boolean sortAscending = true;
/**
* Updates the contents of the table model with the
* {@code TableViewEntryPanel.searchResult} object.
*/
public void displayEntry()
{
updateDataArray();
fireTableDataChanged();
}
/**
* Updates the table model contents and sorts its contents depending on the
* sort options set by the user.
*/
public void forceResort()
{
updateDataArray();
fireTableDataChanged();
}
/**
* {@inheritDoc}
*/
public int compare(AttributeValuePair desc1, AttributeValuePair desc2)
{
int result;
int[] possibleResults = {
desc1.attrName.compareTo(desc2.attrName),
compareValues(desc1.value, desc2.value)};
result = possibleResults[sortColumn];
if (result == 0)
{
for (int i : possibleResults)
{
if (i != 0)
{
result = i;
break;
}
}
}
if (!sortAscending)
{
result = -result;
}
return result;
}
private int compareValues(Object o1, Object o2)
{
if (o1 == null)
{
if (o2 == null)
{
return 0;
}
else
{
return -1;
}
}
else if (o2 == null)
{
return 1;
}
if (o1 instanceof ObjectClassValue)
{
o1 = renderer.getString((ObjectClassValue)o1);
}
else if (o1 instanceof BinaryValue)
{
o1 = renderer.getString((BinaryValue)o1);
}
else if (o1 instanceof byte[])
{
o1 = renderer.getString((byte[])o1);
}
if (o2 instanceof ObjectClassValue)
{
o2 = renderer.getString((ObjectClassValue)o2);
}
else if (o2 instanceof BinaryValue)
{
o2 = renderer.getString((BinaryValue)o2);
}
else if (o2 instanceof byte[])
{
o2 = renderer.getString((byte[])o2);
}
if (o1.getClass().equals(o2.getClass()))
{
if (o1 instanceof String)
{
return ((String)o1).compareTo((String)o2);
}
else if (o1 instanceof Integer)
{
return ((Integer)o1).compareTo((Integer)o2);
}
else if (o1 instanceof Long)
{
return ((Long)o1).compareTo((Long)o2);
}
else
{
return String.valueOf(o1).compareTo(String.valueOf(o2));
}
}
else
{
return String.valueOf(o1).compareTo(String.valueOf(o2));
}
}
/**
* {@inheritDoc}
*/
public int getColumnCount()
{
return COLUMN_NAMES.length;
}
/**
* {@inheritDoc}
*/
public int getRowCount()
{
return dataArray.size();
}
/**
* {@inheritDoc}
*/
public Object getValueAt(int row, int col)
{
if (col == 0)
{
return dataArray.get(row).attrName;
}
else
{
return dataArray.get(row).value;
}
}
/**
* {@inheritDoc}
*/
public String getColumnName(int col) {
return COLUMN_NAMES[col];
}
/**
* Returns whether the sort is ascending or descending.
* @return <CODE>true</CODE> if the sort is ascending and <CODE>false</CODE>
* otherwise.
*/
public boolean isSortAscending()
{
return sortAscending;
}
/**
* Sets whether to sort ascending of descending.
* @param sortAscending whether to sort ascending or descending.
*/
public void setSortAscending(boolean sortAscending)
{
this.sortAscending = sortAscending;
}
/**
* Returns the column index used to sort.
* @return the column index used to sort.
*/
public int getSortColumn()
{
return sortColumn;
}
/**
* Sets the column index used to sort.
* @param sortColumn column index used to sort..
*/
public void setSortColumn(int sortColumn)
{
this.sortColumn = sortColumn;
}
/**
* {@inheritDoc}
*/
public boolean isCellEditable(int row, int col) {
if (col == 0)
{
return false;
}
else
{
if (!isReadOnly)
{
return !schemaReadOnlyAttributesLowerCase.contains(
dataArray.get(row).attrName.toLowerCase());
}
else
{
return false;
}
}
}
/**
* {@inheritDoc}
*/
public void setValueAt(Object value, int row, int col)
{
dataArray.get(row).value = value;
if (value instanceof ObjectClassValue)
{
updateObjectClass((ObjectClassValue)value);
}
else
{
fireTableCellUpdated(row, col);
notifyListeners();
}
}
private void updateDataArray()
{
allSortedValues.clear();
requiredAttrs.clear();
List<String> addedAttrs = new ArrayList<String>();
Schema schema = getInfo().getServerDescriptor().getSchema();
List<Object> ocs = null;
for (String attrName : searchResult.getAttributeNames())
{
if (attrName.equalsIgnoreCase(
ServerConstants.OBJECTCLASS_ATTRIBUTE_TYPE_NAME))
{
if (schema != null)
{
ocs = searchResult.getAttributeValues(attrName);
ObjectClassValue ocValue = getObjectClassDescriptor(
ocs, schema);
allSortedValues.add(new AttributeValuePair(attrName, ocValue));
}
}
else
{
for (Object v : searchResult.getAttributeValues(attrName))
{
allSortedValues.add(new AttributeValuePair(attrName, v));
}
}
addedAttrs.add(
Utilities.getAttributeNameWithoutOptions(attrName).toLowerCase());
}
if ((ocs != null) && (schema != null))
{
for (Object o : ocs)
{
String oc = (String)o;
ObjectClass objectClass = schema.getObjectClass(oc.toLowerCase());
if (objectClass != null)
{
for (AttributeType attr : objectClass.getRequiredAttributeChain())
{
String attrName = attr.getNameOrOID();
if (!addedAttrs.contains(attrName.toLowerCase()))
{
if (isBinary(attrName) || isPassword(attrName))
{
allSortedValues.add(new AttributeValuePair(attrName,
new byte[]{}));
}
else
{
allSortedValues.add(new AttributeValuePair(attrName, ""));
}
}
requiredAttrs.add(attrName.toLowerCase());
}
for (AttributeType attr : objectClass.getOptionalAttributeChain())
{
String attrName = attr.getNameOrOID();
if (!addedAttrs.contains(attrName.toLowerCase()))
{
if (isBinary(attrName) || isPassword(attrName))
{
allSortedValues.add(new AttributeValuePair(attrName,
new byte[]{}));
}
else
{
allSortedValues.add(new AttributeValuePair(attrName, ""));
}
}
}
}
}
}
dataArray.clear();
for (AttributeValuePair value : allSortedValues)
{
if (!showOnlyAttrsWithValues.isSelected() ||
isRequired(value) || hasValue(value))
{
dataArray.add(value);
}
}
renderer.setRequiredAttrs(requiredAttrs);
}
/**
* Checks if we have to display all the attributes or only those that
* contain a value and updates the contents of the model accordingly. Note
* that even if the required attributes have no value they will be
* displayed.
*
*/
void updateAttributeVisibility()
{
dataArray.clear();
for (AttributeValuePair value : allSortedValues)
{
if (!showOnlyAttrsWithValues.isSelected() ||
isRequired(value) || hasValue(value))
{
dataArray.add(value);
}
}
fireTableDataChanged();
Utilities.updateTableSizes(table);
Utilities.updateScrollMode(scroll, table);
}
/**
* Returns the list of values associated with a given attribute.
* @param attrName the name of the attribute.
* @return the list of values associated with a given attribute.
*/
public List<Object> getValues(String attrName)
{
List<Object> values = new ArrayList<Object>();
for (AttributeValuePair valuePair : dataArray)
{
if (valuePair.attrName.equalsIgnoreCase(attrName))
{
if (hasValue(valuePair))
{
if (valuePair.value instanceof Collection<?>)
{
values.addAll((Collection<?>) valuePair.value);
}
else
{
values.add(valuePair.value);
}
}
}
}
return values;
}
private void updateObjectClass(ObjectClassValue newValue)
{
CustomSearchResult oldResult = searchResult;
CustomSearchResult newResult =
new CustomSearchResult(searchResult.getDN());
for (String attrName : schemaReadOnlyAttributesLowerCase)
{
List<Object> values = searchResult.getAttributeValues(attrName);
if (!values.isEmpty())
{
newResult.set(attrName, values);
}
}
ignoreEntryChangeEvents = true;
Schema schema = getInfo().getServerDescriptor().getSchema();
if (schema != null)
{
ArrayList<String> attributes = new ArrayList<String>();
ArrayList<String> ocs = new ArrayList<String>();
if (newValue.getStructural() != null)
{
ocs.add(newValue.getStructural().toLowerCase());
}
for (String oc : newValue.getAuxiliary())
{
ocs.add(oc.toLowerCase());
}
for (String oc : ocs)
{
ObjectClass objectClass = schema.getObjectClass(oc);
if (objectClass != null)
{
for (AttributeType attr : objectClass.getRequiredAttributeChain())
{
attributes.add(attr.getNameOrOID().toLowerCase());
}
for (AttributeType attr : objectClass.getOptionalAttributeChain())
{
attributes.add(attr.getNameOrOID().toLowerCase());
}
}
}
for (String attrName : editableOperationalAttrNames)
{
attributes.add(attrName.toLowerCase());
}
for (AttributeValuePair currValue : allSortedValues)
{
String attrNoOptions = Utilities.getAttributeNameWithoutOptions(
currValue.attrName).toLowerCase();
if (!attributes.contains(attrNoOptions))
{
continue;
}
else if (!schemaReadOnlyAttributesLowerCase.contains(
currValue.attrName.toLowerCase()))
{
setValues(newResult, currValue.attrName);
}
}
}
update(newResult, isReadOnly, treePath);
ignoreEntryChangeEvents = false;
searchResult = oldResult;
notifyListeners();
}
private boolean isRequired(AttributeValuePair value)
{
return requiredAttrs.contains(
Utilities.getAttributeNameWithoutOptions(
value.attrName.toLowerCase()));
}
private boolean hasValue(AttributeValuePair value)
{
boolean hasValue = value.value != null;
if (hasValue)
{
if (value.value instanceof String)
{
hasValue = ((String)value.value).length() > 0;
}
else if (value.value instanceof byte[])
{
hasValue = ((byte[])value.value).length > 0;
}
}
return hasValue;
}
}
/**
* A simple class that contains an attribute name and a single value. It is
* used by the table model to be able to retrieve more easily all the values
* for a given attribute.
*
*/
class AttributeValuePair
{
/**
* The attribute name.
*/
String attrName;
/**
* The value.
*/
Object value;
/**
* Constructor.
* @param attrName the attribute name.
* @param value the value.
*/
public AttributeValuePair(String attrName, Object value)
{
this.attrName = attrName;
this.value = value;
}
}
}