/* * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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-2009 Sun Microsystems, Inc. */ package org.opends.guitools.controlpanel.ui; import static org.opends.messages.AdminToolMessages.*; import java.awt.Component; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; 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.HashSet; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import javax.naming.ldap.InitialLdapContext; import javax.swing.Box; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.border.EmptyBorder; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.opends.guitools.controlpanel.datamodel.AbstractIndexDescriptor; import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo; import org.opends.guitools.controlpanel.datamodel.IndexDescriptor; import org.opends.guitools.controlpanel.datamodel.ServerDescriptor; import org.opends.guitools.controlpanel.event.ConfigurationChangeEvent; import org.opends.guitools.controlpanel.event.ScrollPaneBorderListener; import org.opends.guitools.controlpanel.task.DeleteIndexTask; import org.opends.guitools.controlpanel.task.OfflineUpdateException; import org.opends.guitools.controlpanel.task.Task; import org.opends.guitools.controlpanel.util.ConfigReader; import org.opends.guitools.controlpanel.util.Utilities; import org.opends.messages.Message; import org.opends.server.admin.client.ManagementContext; import org.opends.server.admin.client.ldap.JNDIDirContextAdaptor; import org.opends.server.admin.client.ldap.LDAPManagementContext; import org.opends.server.admin.std.client.LocalDBBackendCfgClient; import org.opends.server.admin.std.client.LocalDBIndexCfgClient; import org.opends.server.admin.std.client.RootCfgClient; import org.opends.server.admin.std.meta.LocalDBIndexCfgDefn.IndexType; import org.opends.server.core.DirectoryServer; import org.opends.server.types.AttributeType; import org.opends.server.types.DN; import org.opends.server.types.Entry; import org.opends.server.types.LDIFImportConfig; import org.opends.server.types.OpenDsException; import org.opends.server.util.LDIFReader; import org.opends.server.util.ServerConstants; import org.opends.server.util.cli.CommandBuilder; /** * The panel that displays an existing index (it appears on the right of the * 'Manage Indexes' dialog). * */ public class IndexPanel extends AbstractIndexPanel { private static final long serialVersionUID = 1439500626486823366L; private IndexDescriptor index; private ScrollPaneBorderListener scrollListener; private boolean ignoreCheckSave; private ModifyIndexTask newModifyTask; /** * Default constructor. * */ public IndexPanel() { super(); createLayout(); } /** * Creates the layout of the panel (but the contents are not populated here). * */ private void createLayout() { GridBagConstraints gbc = new GridBagConstraints(); JPanel p = new JPanel(new GridBagLayout()); p.setOpaque(false); super.createBasicLayout(p, gbc, true); p.setBorder(new EmptyBorder(10, 10, 10, 10)); gbc = new GridBagConstraints(); gbc.weightx = 1.0; gbc.weighty = 1.0; gbc.fill = GridBagConstraints.BOTH; gbc.gridx = 0; gbc.gridy = 0; JScrollPane scroll = Utilities.createBorderLessScrollBar(p); scrollListener = ScrollPaneBorderListener.createBottomBorderListener(scroll); add(scroll, gbc); gbc.gridy ++; gbc.gridx = 0; gbc.weightx = 1.0; gbc.insets.left = 0; gbc.gridwidth = 2; gbc.weighty = 0.0; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(10, 10, 0, 10); add(warning, gbc); Utilities.setWarningLabel(warning, INDEX_MODIFIED); gbc.gridy ++; JPanel buttonPanel = new JPanel(new GridBagLayout()); buttonPanel.setOpaque(false); gbc.insets = new Insets(10, 10, 10, 10); add(buttonPanel, gbc); gbc.insets = new Insets(0, 0, 0, 0); gbc.gridy = 0; gbc.gridx = 0; gbc.weightx = 0.0; gbc.gridwidth = 1; deleteIndex.setOpaque(false); gbc.insets.left = 0; buttonPanel.add(deleteIndex, gbc); deleteIndex.addActionListener(new ActionListener() { /** * {@inheritDoc} */ public void actionPerformed(ActionEvent ev) { deleteIndex(); } }); gbc.gridx = 2; gbc.weightx = 1.0; buttonPanel.add(Box.createHorizontalGlue(), gbc); gbc.weightx = 0.0; gbc.insets.left = 10; saveChanges.setOpaque(false); gbc.gridx = 3; buttonPanel.add(saveChanges, gbc); saveChanges.addActionListener(new ActionListener() { /** * {@inheritDoc} */ public void actionPerformed(ActionEvent ev) { saveIndex(false); } }); entryLimit.getDocument().addDocumentListener(new DocumentListener() { /** * {@inheritDoc} */ public void insertUpdate(DocumentEvent ev) { checkSaveButton(); } /** * {@inheritDoc} */ public void changedUpdate(DocumentEvent ev) { checkSaveButton(); } /** * {@inheritDoc} */ public void removeUpdate(DocumentEvent ev) { checkSaveButton(); } }); ActionListener listener = new ActionListener() { /** * {@inheritDoc} */ public void actionPerformed(ActionEvent ev) { checkSaveButton(); } }; for (JCheckBox cb : types) { cb.addActionListener(listener); } } /** * {@inheritDoc} */ public Message getTitle() { return INFO_CTRL_PANEL_INDEX_PANEL_TITLE.get(); } /** * {@inheritDoc} */ public Component getPreferredFocusComponent() { return entryLimit; } /** * {@inheritDoc} */ public void configurationChanged(ConfigurationChangeEvent ev) { final ServerDescriptor desc = ev.getNewDescriptor(); updateErrorPaneIfAuthRequired(desc, isLocal() ? INFO_CTRL_PANEL_AUTHENTICATION_REQUIRED_FOR_INDEX_EDITING.get() : INFO_CTRL_PANEL_CANNOT_CONNECT_TO_REMOTE_DETAILS.get(desc.getHostname())); SwingUtilities.invokeLater(new Runnable() { /** * {@inheritDoc} */ public void run() { checkSaveButton(); deleteIndex.setEnabled(!authenticationRequired(desc)); } }); } /** * {@inheritDoc} */ public void okClicked() { } /** * Method used to know if there are unsaved changes or not. It is used by * the index selection listener when the user changes the selection. * @return <CODE>true</CODE> if there are unsaved changes (and so the * selection of the index should be canceled) and <CODE>false</CODE> * otherwise. */ public boolean mustCheckUnsavedChanges() { return (index != null) && saveChanges.isVisible() && saveChanges.isEnabled(); } /** * Tells whether the user chose to save the changes in the panel, to not save * them or simply cancelled the selection in the tree. * @return the value telling whether the user chose to save the changes in the * panel, to not save them or simply cancelled the selection change in the * tree. */ public UnsavedChangesDialog.Result checkUnsavedChanges() { UnsavedChangesDialog.Result result; UnsavedChangesDialog unsavedChangesDlg = new UnsavedChangesDialog( Utilities.getParentDialog(this), getInfo()); unsavedChangesDlg.setMessage(INFO_CTRL_PANEL_UNSAVED_CHANGES_SUMMARY.get(), INFO_CTRL_PANEL_UNSAVED_INDEX_CHANGES_DETAILS.get(index.getName())); Utilities.centerGoldenMean(unsavedChangesDlg, Utilities.getParentDialog(this)); unsavedChangesDlg.setVisible(true); result = unsavedChangesDlg.getResult(); if (result == UnsavedChangesDialog.Result.SAVE) { saveIndex(false); if ((newModifyTask == null) || // The user data is not valid (newModifyTask.getState() != Task.State.FINISHED_SUCCESSFULLY)) { result = UnsavedChangesDialog.Result.CANCEL; } } return result; } /** * Checks the enabling state of the save button. * */ private void checkSaveButton() { if (!ignoreCheckSave && (index != null)) { saveChanges.setEnabled( !authenticationRequired(getInfo().getServerDescriptor()) && isModified()); } } private void deleteIndex() { ArrayList<Message> errors = new ArrayList<Message>(); ProgressDialog dlg = new ProgressDialog( Utilities.createFrame(), Utilities.getParentDialog(this), INFO_CTRL_PANEL_DELETE_INDEX_TITLE.get(), getInfo()); ArrayList<AbstractIndexDescriptor> indexesToDelete = new ArrayList<AbstractIndexDescriptor>(); indexesToDelete.add(index); DeleteIndexTask newTask = new DeleteIndexTask(getInfo(), dlg, indexesToDelete); for (Task task : getInfo().getTasks()) { task.canLaunch(newTask, errors); } if (errors.isEmpty()) { String indexName = index.getName(); String backendName = index.getBackend().getBackendID(); if (displayConfirmationDialog( INFO_CTRL_PANEL_CONFIRMATION_REQUIRED_SUMMARY.get(), INFO_CTRL_PANEL_CONFIRMATION_INDEX_DELETE_DETAILS.get(indexName, backendName))) { launchOperation(newTask, INFO_CTRL_PANEL_DELETING_INDEX_SUMMARY.get(), INFO_CTRL_PANEL_DELETING_INDEX_COMPLETE.get(), INFO_CTRL_PANEL_DELETING_INDEX_SUCCESSFUL.get(indexName, backendName), ERR_CTRL_PANEL_DELETING_INDEX_ERROR_SUMMARY.get(), ERR_CTRL_PANEL_DELETING_INDEX_ERROR_DETAILS.get(indexName), null, dlg); dlg.setVisible(true); } } else { displayErrorDialog(errors); } } /** * Saves the index modifications. * @param modal whether the progress dialog for the task must be modal or * not. */ private void saveIndex(boolean modal) { newModifyTask = null; if (!isModified()) { return; } List<Message> errors = getErrors(); if (errors.isEmpty()) { ProgressDialog dlg = new ProgressDialog( Utilities.getFrame(this), Utilities.getFrame(this), INFO_CTRL_PANEL_MODIFYING_INDEX_TITLE.get(), getInfo()); dlg.setModal(modal); newModifyTask = new ModifyIndexTask(getInfo(), dlg); for (Task task : getInfo().getTasks()) { task.canLaunch(newModifyTask, errors); } if (errors.size() == 0) { String attributeName = index.getName(); String backendName = index.getBackend().getBackendID(); launchOperation(newModifyTask, INFO_CTRL_PANEL_MODIFYING_INDEX_SUMMARY.get(attributeName), INFO_CTRL_PANEL_MODIFYING_INDEX_COMPLETE.get(), INFO_CTRL_PANEL_MODIFYING_INDEX_SUCCESSFUL.get(attributeName, backendName), ERR_CTRL_PANEL_MODIFYING_INDEX_ERROR_SUMMARY.get(), ERR_CTRL_PANEL_MODIFYING_INDEX_ERROR_DETAILS.get(attributeName), null, dlg); saveChanges.setEnabled(false); dlg.setVisible(true); } } if (errors.size() > 0) { displayErrorDialog(errors); } } /** * Updates the contents of the panel with the provided index. * @param index the index descriptor to be used to update the panel. */ public void update(IndexDescriptor index) { ignoreCheckSave = true; setPrimaryValid(lEntryLimit); setPrimaryValid(lType); name.setText(index.getName()); backendName.setText(index.getBackend().getBackendID()); titlePanel.setDetails(Message.raw(index.getName())); entryLimit.setText(String.valueOf(index.getEntryLimit())); approximate.setSelected(false); equality.setSelected(false); ordering.setSelected(false); substring.setSelected(false); presence.setSelected(false); for (IndexType type : index.getTypes()) { switch(type) { case APPROXIMATE: approximate.setSelected(true); break; case PRESENCE: presence.setSelected(true); break; case EQUALITY: equality.setSelected(true); break; case ORDERING: ordering.setSelected(true); break; case SUBSTRING: substring.setSelected(true); break; } } JComponent[] comps = {entryLimit, lType, typesPanel, lEntryLimit}; for (int i=0; i<comps.length; i++) { comps[i].setVisible(!index.isDatabaseIndex()); } AttributeType attr = index.getAttributeType(); repopulateTypesPanel(attr); if (index.isDatabaseIndex()) { entryLimit.setText(""); } saveChanges.setVisible(!index.isDatabaseIndex()); deleteIndex.setVisible(!index.isDatabaseIndex()); if (index.isDatabaseIndex()) { Utilities.setWarningLabel(warning, NON_CONFIGURABLE_INDEX); warning.setVisible(true); } else if (getInfo() != null) { if (getInfo().mustReindex(index)) { Utilities.setWarningLabel(warning, INDEX_MODIFIED); warning.setVisible(true); warning.setVerticalTextPosition(SwingConstants.TOP); } else { warning.setVisible(false); } } this.index = index; ignoreCheckSave = false; checkSaveButton(); scrollListener.updateBorder(); } /** * Returns <CODE>true</CODE> if the index has been modified and * <CODE>false</CODE> otherwise. * @return <CODE>true</CODE> if the index has been modified and * <CODE>false</CODE> otherwise. */ private boolean isModified() { return !getTypes().equals(index.getTypes()) || !String.valueOf(index.getEntryLimit()).equals(entryLimit.getText()); } /** * The task in charge of modifying the index. * */ protected class ModifyIndexTask extends Task { private Set<String> backendSet; private String attributeName; private String backendName; private int entryLimitValue; private IndexDescriptor indexToModify; private SortedSet<IndexType> indexTypes = new TreeSet<IndexType>(); private IndexDescriptor modifiedIndex; /** * The constructor of the task. * @param info the control panel info. * @param dlg the progress dialog that shows the progress of the task. */ public ModifyIndexTask(ControlPanelInfo info, ProgressDialog dlg) { super(info, dlg); backendName = index.getBackend().getBackendID(); backendSet = new HashSet<String>(); backendSet.add(backendName); attributeName = index.getName(); entryLimitValue = Integer.parseInt(entryLimit.getText()); indexTypes = getTypes(); indexToModify = index; } /** * {@inheritDoc} */ public Type getType() { return Type.MODIFY_INDEX; } /** * {@inheritDoc} */ public Set<String> getBackends() { return backendSet; } /** * {@inheritDoc} */ public Message getTaskDescription() { return INFO_CTRL_PANEL_MODIFY_INDEX_TASK_DESCRIPTION.get(attributeName, backendName); } /** * {@inheritDoc} */ public boolean canLaunch(Task taskToBeLaunched, Collection<Message> incompatibilityReasons) { boolean canLaunch = true; if (state == State.RUNNING && runningOnSameServer(taskToBeLaunched)) { // All the operations are incompatible if they apply to this // backend for safety. This is a short operation so the limitation // has not a lot of impact. Set<String> backends = new TreeSet<String>(taskToBeLaunched.getBackends()); backends.retainAll(getBackends()); if (backends.size() > 0) { incompatibilityReasons.add(getIncompatibilityMessage(this, taskToBeLaunched)); canLaunch = false; } } return canLaunch; } /** * Updates the configuration of the modified index. * @throws OpenDsException if there is an error updating the configuration. */ private void updateConfiguration() throws OpenDsException { boolean configHandlerUpdated = false; try { if (!isServerRunning()) { configHandlerUpdated = true; getInfo().stopPooling(); if (getInfo().mustDeregisterConfig()) { DirectoryServer.deregisterBaseDN(DN.decode("cn=config")); } DirectoryServer.getInstance().initializeConfiguration( org.opends.server.extensions.ConfigFileHandler.class.getName(), ConfigReader.configFile); getInfo().setMustDeregisterConfig(true); } else { SwingUtilities.invokeLater(new Runnable() { /** * {@inheritDoc} */ public void run() { StringBuilder sb = new StringBuilder(); sb.append(getConfigCommandLineName()); List<String> args = getObfuscatedCommandLineArguments( getDSConfigCommandLineArguments()); args.removeAll(getConfigCommandLineArguments()); printEquivalentCommandLine(getConfigCommandLineName(), args, INFO_CTRL_PANEL_EQUIVALENT_CMD_TO_MODIFY_INDEX.get()); } }); } SwingUtilities.invokeLater(new Runnable() { /** * {@inheritDoc} */ public void run() { getProgressDialog().appendProgressHtml( Utilities.getProgressWithPoints( INFO_CTRL_PANEL_MODIFYING_INDEX_PROGRESS.get(attributeName), ColorAndFontConstants.progressFont)); } }); if (isServerRunning()) { // Create additional indexes and display the equivalent command. // Everything is done in the method createAdditionalIndexes modifyIndex(getInfo().getDirContext()); } else { modifyIndex(); } SwingUtilities.invokeLater(new Runnable() { /** * {@inheritDoc} */ public void run() { getProgressDialog().appendProgressHtml( Utilities.getProgressDone(ColorAndFontConstants.progressFont)); } }); } finally { if (configHandlerUpdated) { DirectoryServer.getInstance().initializeConfiguration( ConfigReader.configClassName, ConfigReader.configFile); getInfo().startPooling(); } } } /** * Returns the LDIF representation of the modified index. * @return the LDIF representation of the modified index. */ private String getIndexLDIF() { String dn = Utilities.getRDNString("ds-cfg-backend-id", backendName)+ ",cn=Backends,cn=config"; ArrayList<String> lines = new ArrayList<String>(); lines.add("dn: "+ Utilities.getRDNString("ds-cfg-attribute", attributeName)+ ",cn=Index,"+dn); lines.add("objectClass: ds-cfg-local-db-index"); lines.add("objectClass: top"); lines.add("ds-cfg-attribute: "+attributeName); lines.add("ds-cfg-index-entry-limit: "+entryLimitValue); for (IndexType type : indexTypes) { lines.add("ds-cfg-index-type: "+type.toString()); } StringBuilder sb = new StringBuilder(); for (String line : lines) { sb.append(line+ServerConstants.EOL); } return sb.toString(); } private void modifyIndex() throws OpenDsException { LDIFImportConfig ldifImportConfig = null; try { String ldif = getIndexLDIF(); ldifImportConfig = new LDIFImportConfig(new StringReader(ldif)); LDIFReader reader = new LDIFReader(ldifImportConfig); Entry newConfigEntry = reader.readEntry(); Entry oldEntry = DirectoryServer.getConfigEntry( newConfigEntry.getDN()).getEntry(); DirectoryServer.getConfigHandler().replaceEntry(oldEntry, newConfigEntry, null); DirectoryServer.getConfigHandler().writeUpdatedConfig(); } catch (IOException ioe) { throw new OfflineUpdateException( ERR_CTRL_PANEL_ERROR_UPDATING_CONFIGURATION.get(ioe.toString()), ioe); } finally { if (ldifImportConfig != null) { ldifImportConfig.close(); } } } /** * Modifies index using the provided connection. * @param ctx the connection to be used to update the index configuration. * @throws OpenDsException if there is an error updating the server. */ private void modifyIndex(InitialLdapContext ctx) throws OpenDsException { final StringBuilder sb = new StringBuilder(); sb.append(getConfigCommandLineName()); Collection<String> args = getObfuscatedCommandLineArguments(getDSConfigCommandLineArguments()); for (String arg : args) { sb.append(" "+CommandBuilder.escapeValue(arg)); } ManagementContext mCtx = LDAPManagementContext.createFromContext( JNDIDirContextAdaptor.adapt(ctx)); RootCfgClient root = mCtx.getRootConfiguration(); LocalDBBackendCfgClient backend = (LocalDBBackendCfgClient)root.getBackend(backendName); LocalDBIndexCfgClient index = backend.getLocalDBIndex(attributeName); if (!indexTypes.equals(indexToModify.getTypes())) { index.setIndexType(indexTypes); } if (entryLimitValue != index.getIndexEntryLimit()) { index.setIndexEntryLimit(entryLimitValue); } index.commit(); } /** * {@inheritDoc} */ protected String getCommandLinePath() { return null; } /** * {@inheritDoc} */ protected ArrayList<String> getCommandLineArguments() { return new ArrayList<String>(); } /** * Returns the full command-line path of the dsconfig command-line if we * can provide an equivalent command-line (the server is running). * @return the full command-line path of the dsconfig command-line if we * can provide an equivalent command-line (the server is running). */ private String getConfigCommandLineName() { if (isServerRunning() && isModified()) { return getCommandLinePath("dsconfig"); } else { return null; } } /** * {@inheritDoc} */ public void runTask() { state = State.RUNNING; lastException = null; try { updateConfiguration(); modifiedIndex = new IndexDescriptor(attributeName, indexToModify.getAttributeType(), indexToModify.getBackend(), indexTypes, entryLimitValue); getInfo().registerModifiedIndex(modifiedIndex); state = State.FINISHED_SUCCESSFULLY; } catch (Throwable t) { lastException = t; state = State.FINISHED_WITH_ERROR; } } /** * {@inheritDoc} */ public void postOperation() { if ((lastException == null) && (state == State.FINISHED_SUCCESSFULLY)) { rebuildIndexIfNecessary(modifiedIndex, getProgressDialog()); } } private ArrayList<String> getDSConfigCommandLineArguments() { ArrayList<String> args = new ArrayList<String>(); args.add("set-local-db-index-prop"); args.add("--backend-name"); args.add(backendName); args.add("--index-name"); args.add(attributeName); if (!indexTypes.equals(indexToModify.getTypes())) { // To add Set<IndexType> toAdd = new TreeSet<IndexType>(); for (IndexType newType : indexTypes) { if (!indexToModify.getTypes().contains(newType)) { toAdd.add(newType); } } // To delete Set<IndexType> toDelete = new TreeSet<IndexType>(); for (IndexType oldType : indexToModify.getTypes()) { if (!indexTypes.contains(oldType)) { toDelete.add(oldType); } } for (IndexType newType : toAdd) { args.add("--add"); args.add("index-type:"+newType.toString()); } for (IndexType newType : toDelete) { args.add("--remove"); args.add("index-type:"+newType.toString()); } } if (entryLimitValue != indexToModify.getEntryLimit()) { args.add("--set"); args.add("index-entry-limit:"+entryLimitValue); } args.addAll(getConnectionCommandLineArguments()); args.add("--no-prompt"); return args; } } }