001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2008-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.guitools.controlpanel.ui;
018
019import static org.opends.messages.AdminToolMessages.*;
020
021import java.awt.Component;
022import java.awt.GridBagConstraints;
023import java.awt.GridBagLayout;
024import java.awt.Insets;
025import java.awt.event.ActionEvent;
026import java.awt.event.ActionListener;
027import java.util.ArrayList;
028import java.util.Collection;
029import java.util.HashSet;
030import java.util.List;
031import java.util.Set;
032import java.util.SortedSet;
033import java.util.TreeSet;
034
035import javax.naming.ldap.InitialLdapContext;
036import javax.swing.Box;
037import javax.swing.JCheckBox;
038import javax.swing.JComponent;
039import javax.swing.JPanel;
040import javax.swing.JScrollPane;
041import javax.swing.SwingConstants;
042import javax.swing.SwingUtilities;
043import javax.swing.border.EmptyBorder;
044import javax.swing.event.DocumentEvent;
045import javax.swing.event.DocumentListener;
046
047import org.forgerock.i18n.LocalizableMessage;
048import org.opends.guitools.controlpanel.datamodel.AbstractIndexDescriptor;
049import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
050import org.opends.guitools.controlpanel.datamodel.IndexDescriptor;
051import org.opends.guitools.controlpanel.datamodel.IndexTypeDescriptor;
052import org.opends.guitools.controlpanel.datamodel.ServerDescriptor;
053import org.opends.guitools.controlpanel.event.ConfigurationChangeEvent;
054import org.opends.guitools.controlpanel.event.ScrollPaneBorderListener;
055import org.opends.guitools.controlpanel.task.DeleteIndexTask;
056import org.opends.guitools.controlpanel.task.Task;
057import org.opends.guitools.controlpanel.util.ConfigReader;
058import org.opends.guitools.controlpanel.util.Utilities;
059import org.opends.server.admin.client.ManagementContext;
060import org.opends.server.admin.client.ldap.JNDIDirContextAdaptor;
061import org.opends.server.admin.client.ldap.LDAPManagementContext;
062import org.opends.server.admin.std.client.BackendCfgClient;
063import org.opends.server.admin.std.client.BackendIndexCfgClient;
064import org.opends.server.admin.std.client.PluggableBackendCfgClient;
065import org.opends.server.core.DirectoryServer;
066import org.forgerock.opendj.ldap.schema.AttributeType;
067import org.forgerock.opendj.ldap.DN;
068import org.opends.server.types.OpenDsException;
069
070/**
071 * The panel that displays an existing index (it appears on the right of the
072 * 'Manage Indexes' dialog).
073 */
074public class IndexPanel extends AbstractIndexPanel
075{
076  private static final long serialVersionUID = 1439500626486823366L;
077
078  private IndexDescriptor index;
079  private ScrollPaneBorderListener scrollListener;
080
081  private boolean ignoreCheckSave;
082
083  private ModifyIndexTask newModifyTask;
084
085  /** Default constructor. */
086  public IndexPanel()
087  {
088    super();
089    createLayout();
090  }
091
092  /**
093   * Creates the layout of the panel (but the contents are not populated here).
094   */
095  private void createLayout()
096  {
097    GridBagConstraints gbc = new GridBagConstraints();
098    JPanel p = new JPanel(new GridBagLayout());
099    p.setOpaque(false);
100    super.createBasicLayout(p, gbc, true);
101    p.setBorder(new EmptyBorder(10, 10, 10, 10));
102    gbc = new GridBagConstraints();
103    gbc.weightx = 1.0;
104    gbc.weighty = 1.0;
105    gbc.fill = GridBagConstraints.BOTH;
106    gbc.gridx = 0;
107    gbc.gridy = 0;
108    JScrollPane scroll = Utilities.createBorderLessScrollBar(p);
109    scrollListener =
110      ScrollPaneBorderListener.createBottomBorderListener(scroll);
111    add(scroll, gbc);
112
113    gbc.gridy ++;
114    gbc.gridx = 0;
115    gbc.weightx = 1.0;
116    gbc.insets.left = 0;
117    gbc.gridwidth = 2;
118    gbc.weighty = 0.0;
119    gbc.fill = GridBagConstraints.HORIZONTAL;
120
121    gbc.insets = new Insets(10, 10, 0, 10);
122    add(warning, gbc);
123    Utilities.setWarningLabel(warning, INDEX_MODIFIED);
124
125    gbc.gridy ++;
126    JPanel buttonPanel = new JPanel(new GridBagLayout());
127    buttonPanel.setOpaque(false);
128    gbc.insets = new Insets(10, 10, 10, 10);
129    add(buttonPanel, gbc);
130
131    gbc.insets = new Insets(0, 0, 0, 0);
132    gbc.gridy = 0;
133    gbc.gridx = 0;
134    gbc.weightx = 0.0;
135    gbc.gridwidth = 1;
136    deleteIndex.setOpaque(false);
137    gbc.insets.left = 0;
138    buttonPanel.add(deleteIndex, gbc);
139    deleteIndex.addActionListener(new ActionListener()
140    {
141      @Override
142      public void actionPerformed(ActionEvent ev)
143      {
144        deleteIndex();
145      }
146    });
147    gbc.gridx = 2;
148    gbc.weightx = 1.0;
149    buttonPanel.add(Box.createHorizontalGlue(), gbc);
150    gbc.weightx = 0.0;
151    gbc.insets.left = 10;
152    saveChanges.setOpaque(false);
153    gbc.gridx = 3;
154    buttonPanel.add(saveChanges, gbc);
155    saveChanges.addActionListener(new ActionListener()
156    {
157      @Override
158      public void actionPerformed(ActionEvent ev)
159      {
160        saveIndex(false);
161      }
162    });
163
164    entryLimit.getDocument().addDocumentListener(new DocumentListener()
165    {
166      @Override
167      public void insertUpdate(DocumentEvent ev)
168      {
169        checkSaveButton();
170      }
171
172      @Override
173      public void changedUpdate(DocumentEvent ev)
174      {
175        checkSaveButton();
176      }
177
178      @Override
179      public void removeUpdate(DocumentEvent ev)
180      {
181        checkSaveButton();
182      }
183    });
184
185    ActionListener listener = new ActionListener()
186    {
187      @Override
188      public void actionPerformed(ActionEvent ev)
189      {
190        checkSaveButton();
191      }
192    };
193    for (JCheckBox cb : types)
194    {
195      cb.addActionListener(listener);
196    }
197  }
198
199  @Override
200  public LocalizableMessage getTitle()
201  {
202    return INFO_CTRL_PANEL_INDEX_PANEL_TITLE.get();
203  }
204
205  @Override
206  public Component getPreferredFocusComponent()
207  {
208    return entryLimit;
209  }
210
211  @Override
212  public void configurationChanged(ConfigurationChangeEvent ev)
213  {
214    final ServerDescriptor desc = ev.getNewDescriptor();
215    updateErrorPaneIfAuthRequired(desc,
216        isLocal() ?
217            INFO_CTRL_PANEL_AUTHENTICATION_REQUIRED_FOR_INDEX_EDITING.get() :
218      INFO_CTRL_PANEL_CANNOT_CONNECT_TO_REMOTE_DETAILS.get(desc.getHostname()));
219    SwingUtilities.invokeLater(new Runnable()
220    {
221      @Override
222      public void run()
223      {
224        checkSaveButton();
225        deleteIndex.setEnabled(!authenticationRequired(desc));
226      }
227    });
228  }
229
230  @Override
231  public void okClicked()
232  {
233  }
234
235  /**
236   * Method used to know if there are unsaved changes or not. It is used by the
237   * index selection listener when the user changes the selection.
238   *
239   * @return <CODE>true</CODE> if there are unsaved changes (and so the
240   *         selection of the index should be canceled) and <CODE>false</CODE>
241   *         otherwise.
242   */
243  public boolean mustCheckUnsavedChanges()
244  {
245    return index != null &&
246        saveChanges.isVisible() && saveChanges.isEnabled();
247  }
248
249  /**
250   * Tells whether the user chose to save the changes in the panel, to not save
251   * them or simply cancelled the selection in the tree.
252   *
253   * @return the value telling whether the user chose to save the changes in the
254   *         panel, to not save them or simply cancelled the selection change in
255   *         the tree.
256   */
257  public UnsavedChangesDialog.Result checkUnsavedChanges()
258  {
259    UnsavedChangesDialog.Result result;
260    UnsavedChangesDialog unsavedChangesDlg = new UnsavedChangesDialog(Utilities.getParentDialog(this), getInfo());
261    unsavedChangesDlg.setMessage(INFO_CTRL_PANEL_UNSAVED_CHANGES_SUMMARY.get(),
262                                 INFO_CTRL_PANEL_UNSAVED_INDEX_CHANGES_DETAILS.get(index.getName()));
263    Utilities.centerGoldenMean(unsavedChangesDlg, Utilities.getParentDialog(this));
264    unsavedChangesDlg.setVisible(true);
265    result = unsavedChangesDlg.getResult();
266    if (result == UnsavedChangesDialog.Result.SAVE)
267    {
268      saveIndex(false);
269      if (newModifyTask == null || // The user data is not valid
270          newModifyTask.getState() != Task.State.FINISHED_SUCCESSFULLY)
271      {
272        result = UnsavedChangesDialog.Result.CANCEL;
273      }
274    }
275
276    return result;
277  }
278
279  /** Checks the enabling state of the save button. */
280  private void checkSaveButton()
281  {
282    if (!ignoreCheckSave && index != null)
283    {
284      saveChanges.setEnabled(
285          !authenticationRequired(getInfo().getServerDescriptor()) &&
286          isModified());
287    }
288  }
289
290  private void deleteIndex()
291  {
292    List<LocalizableMessage> errors = new ArrayList<>();
293    ProgressDialog dlg = new ProgressDialog(
294        Utilities.createFrame(),
295        Utilities.getParentDialog(this),
296        INFO_CTRL_PANEL_DELETE_INDEX_TITLE.get(), getInfo());
297    ArrayList<AbstractIndexDescriptor> indexesToDelete = new ArrayList<>();
298    indexesToDelete.add(index);
299    DeleteIndexTask newTask = new DeleteIndexTask(getInfo(), dlg, indexesToDelete);
300    for (Task task : getInfo().getTasks())
301    {
302      task.canLaunch(newTask, errors);
303    }
304
305    if (errors.isEmpty())
306    {
307      String indexName = index.getName();
308      String backendName = index.getBackend().getBackendID();
309      if (displayConfirmationDialog(INFO_CTRL_PANEL_CONFIRMATION_REQUIRED_SUMMARY.get(),
310                                    INFO_CTRL_PANEL_CONFIRMATION_INDEX_DELETE_DETAILS.get(indexName, backendName)))
311      {
312        launchOperation(newTask,
313            INFO_CTRL_PANEL_DELETING_INDEX_SUMMARY.get(),
314            INFO_CTRL_PANEL_DELETING_INDEX_COMPLETE.get(),
315            INFO_CTRL_PANEL_DELETING_INDEX_SUCCESSFUL.get(indexName, backendName),
316            ERR_CTRL_PANEL_DELETING_INDEX_ERROR_SUMMARY.get(),
317            ERR_CTRL_PANEL_DELETING_INDEX_ERROR_DETAILS.get(indexName), null, dlg);
318        dlg.setVisible(true);
319      }
320    }
321    else
322    {
323      displayErrorDialog(errors);
324    }
325  }
326
327  /**
328   * Saves the index modifications.
329   *
330   * @param modal
331   *          whether the progress dialog for the task must be modal or not.
332   */
333  private void saveIndex(boolean modal)
334  {
335    newModifyTask = null;
336    if (!isModified())
337    {
338      return;
339    }
340
341    List<LocalizableMessage> errors = getErrors();
342
343    if (errors.isEmpty())
344    {
345      ProgressDialog dlg = new ProgressDialog(
346          Utilities.getFrame(this),
347          Utilities.getFrame(this),
348          INFO_CTRL_PANEL_MODIFYING_INDEX_TITLE.get(), getInfo());
349      dlg.setModal(modal);
350      newModifyTask = new ModifyIndexTask(getInfo(), dlg);
351      for (Task task : getInfo().getTasks())
352      {
353        task.canLaunch(newModifyTask, errors);
354      }
355      if (errors.isEmpty())
356      {
357        String attributeName = index.getName();
358        String backendName = index.getBackend().getBackendID();
359        launchOperation(newModifyTask,
360            INFO_CTRL_PANEL_MODIFYING_INDEX_SUMMARY.get(attributeName),
361            INFO_CTRL_PANEL_MODIFYING_INDEX_COMPLETE.get(),
362            INFO_CTRL_PANEL_MODIFYING_INDEX_SUCCESSFUL.get(attributeName, backendName),
363            ERR_CTRL_PANEL_MODIFYING_INDEX_ERROR_SUMMARY.get(),
364            ERR_CTRL_PANEL_MODIFYING_INDEX_ERROR_DETAILS.get(attributeName), null, dlg);
365        saveChanges.setEnabled(false);
366        dlg.setVisible(true);
367      }
368    }
369
370    if (!errors.isEmpty())
371    {
372      displayErrorDialog(errors);
373    }
374  }
375
376  /**
377   * Updates the contents of the panel with the provided index.
378   *
379   * @param index
380   *          the index descriptor to be used to update the panel.
381   */
382  public void update(IndexDescriptor index)
383  {
384    ignoreCheckSave = true;
385    setPrimaryValid(lEntryLimit);
386    setPrimaryValid(lType);
387    name.setText(index.getName());
388    backendName.setText(index.getBackend().getBackendID());
389    titlePanel.setDetails(LocalizableMessage.raw(index.getName()));
390    entryLimit.setText(String.valueOf(index.getEntryLimit()));
391    approximate.setSelected(false);
392    equality.setSelected(false);
393    ordering.setSelected(false);
394    substring.setSelected(false);
395    presence.setSelected(false);
396    for (IndexTypeDescriptor type : index.getTypes())
397    {
398      switch(type)
399      {
400      case APPROXIMATE:
401        approximate.setSelected(true);
402        break;
403      case PRESENCE:
404        presence.setSelected(true);
405        break;
406      case EQUALITY:
407        equality.setSelected(true);
408        break;
409      case ORDERING:
410        ordering.setSelected(true);
411        break;
412      case SUBSTRING:
413        substring.setSelected(true);
414        break;
415      }
416    }
417
418    JComponent[] comps = {entryLimit, lType, typesPanel, lEntryLimit};
419
420    for (JComponent comp : comps)
421    {
422      comp.setVisible(!index.isDatabaseIndex());
423    }
424
425    AttributeType attr = index.getAttributeType();
426    repopulateTypesPanel(attr);
427
428    if (index.isDatabaseIndex())
429    {
430      entryLimit.setText("");
431    }
432    saveChanges.setVisible(!index.isDatabaseIndex());
433    deleteIndex.setVisible(!index.isDatabaseIndex());
434    if (index.isDatabaseIndex())
435    {
436      Utilities.setWarningLabel(warning, NON_CONFIGURABLE_INDEX);
437      warning.setVisible(true);
438    }
439    else if (getInfo() != null)
440    {
441      if (getInfo().mustReindex(index))
442      {
443        Utilities.setWarningLabel(warning, INDEX_MODIFIED);
444        warning.setVisible(true);
445        warning.setVerticalTextPosition(SwingConstants.TOP);
446      }
447      else
448      {
449        warning.setVisible(false);
450      }
451    }
452    this.index = index;
453
454    ignoreCheckSave = false;
455    checkSaveButton();
456
457    scrollListener.updateBorder();
458  }
459
460  /**
461   * Returns <CODE>true</CODE> if the index has been modified and
462   * <CODE>false</CODE> otherwise.
463   *
464   * @return <CODE>true</CODE> if the index has been modified and
465   *         <CODE>false</CODE> otherwise.
466   */
467  private boolean isModified()
468  {
469    return !getTypes().equals(index.getTypes()) ||
470    !String.valueOf(index.getEntryLimit()).equals(entryLimit.getText());
471  }
472
473  /**
474   * The task in charge of modifying the index.
475   */
476  protected class ModifyIndexTask extends Task
477  {
478    private Set<String> backendSet;
479    private String attributeName;
480    private String backendName;
481    private int entryLimitValue;
482    private IndexDescriptor indexToModify;
483    private SortedSet<IndexTypeDescriptor> indexTypes = new TreeSet<>();
484    private IndexDescriptor modifiedIndex;
485
486    /**
487     * The constructor of the task.
488     *
489     * @param info
490     *          the control panel info.
491     * @param dlg
492     *          the progress dialog that shows the progress of the task.
493     */
494    public ModifyIndexTask(ControlPanelInfo info, ProgressDialog dlg)
495    {
496      super(info, dlg);
497      backendName = index.getBackend().getBackendID();
498      backendSet = new HashSet<>();
499      backendSet.add(backendName);
500      attributeName = index.getName();
501      entryLimitValue = Integer.parseInt(entryLimit.getText());
502      indexTypes = getTypes();
503
504      indexToModify = index;
505    }
506
507    @Override
508    public Type getType()
509    {
510      return Type.MODIFY_INDEX;
511    }
512
513    @Override
514    public Set<String> getBackends()
515    {
516      return backendSet;
517    }
518
519    @Override
520    public LocalizableMessage getTaskDescription()
521    {
522      return INFO_CTRL_PANEL_MODIFY_INDEX_TASK_DESCRIPTION.get(attributeName,
523          backendName);
524    }
525
526    @Override
527    public boolean canLaunch(Task taskToBeLaunched, Collection<LocalizableMessage> incompatibilityReasons)
528    {
529      boolean canLaunch = true;
530      if (state == State.RUNNING && runningOnSameServer(taskToBeLaunched))
531      {
532        // All the operations are incompatible if they apply to this
533        // backend for safety.  This is a short operation so the limitation
534        // has not a lot of impact.
535        Set<String> backends = new TreeSet<>(taskToBeLaunched.getBackends());
536        backends.retainAll(getBackends());
537        if (!backends.isEmpty())
538        {
539          incompatibilityReasons.add(getIncompatibilityMessage(this, taskToBeLaunched));
540          canLaunch = false;
541        }
542      }
543      return canLaunch;
544    }
545
546    /**
547     * Updates the configuration of the modified index.
548     *
549     * @throws OpenDsException
550     *           if there is an error updating the configuration.
551     */
552    private void updateConfiguration() throws OpenDsException
553    {
554      boolean configHandlerUpdated = false;
555      try
556      {
557        if (!isServerRunning())
558        {
559          configHandlerUpdated = true;
560          getInfo().stopPooling();
561          if (getInfo().mustDeregisterConfig())
562          {
563            DirectoryServer.deregisterBaseDN(DN.valueOf("cn=config"));
564          }
565          DirectoryServer.getInstance().initializeConfiguration(
566              org.opends.server.extensions.ConfigFileHandler.class.getName(), ConfigReader.configFile);
567          getInfo().setMustDeregisterConfig(true);
568        }
569        else
570        {
571          SwingUtilities.invokeLater(new Runnable()
572          {
573            @Override
574            public void run()
575            {
576              StringBuilder sb = new StringBuilder();
577              sb.append(getConfigCommandLineName());
578              List<String> args = getObfuscatedCommandLineArguments(getDSConfigCommandLineArguments());
579              args.removeAll(getConfigCommandLineArguments());
580
581              printEquivalentCommandLine(
582                      getConfigCommandLineName(), args, INFO_CTRL_PANEL_EQUIVALENT_CMD_TO_MODIFY_INDEX.get());
583            }
584          });
585        }
586
587        SwingUtilities.invokeLater(new Runnable()
588        {
589          @Override
590          public void run()
591          {
592            getProgressDialog().appendProgressHtml(
593                Utilities.getProgressWithPoints(
594                    INFO_CTRL_PANEL_MODIFYING_INDEX_PROGRESS.get(attributeName),
595                    ColorAndFontConstants.progressFont));
596          }
597        });
598
599        if (isServerRunning())
600        {
601          modifyIndexOnline(getInfo().getDirContext());
602        }
603        else
604        {
605          modifyIndexOffline(backendName, attributeName, indexToModify, indexTypes, entryLimitValue);
606        }
607
608        SwingUtilities.invokeLater(new Runnable()
609        {
610          @Override
611          public void run()
612          {
613            getProgressDialog().appendProgressHtml(
614                Utilities.getProgressDone(ColorAndFontConstants.progressFont));
615          }
616        });
617      }
618      finally
619      {
620        if (configHandlerUpdated)
621        {
622          DirectoryServer.getInstance().initializeConfiguration(ConfigReader.configClassName, ConfigReader.configFile);
623          getInfo().startPooling();
624        }
625      }
626    }
627
628    /**
629     * Modifies index using the provided connection.
630     *
631     * @param ctx
632     *          the connection to be used to update the index configuration.
633     * @throws OpenDsException
634     *           if there is an error updating the server.
635     */
636    private void modifyIndexOnline(final InitialLdapContext ctx) throws OpenDsException
637    {
638      final ManagementContext mCtx = LDAPManagementContext.createFromContext(JNDIDirContextAdaptor.adapt(ctx));
639      final BackendCfgClient backend = mCtx.getRootConfiguration().getBackend(backendName);
640      modifyBackendIndexOnline((PluggableBackendCfgClient) backend);
641    }
642
643    private void modifyBackendIndexOnline(final PluggableBackendCfgClient backend) throws OpenDsException
644    {
645      final BackendIndexCfgClient index = backend.getBackendIndex(attributeName);
646      if (!indexTypes.equals(indexToModify.getTypes()))
647      {
648        index.setIndexType(IndexTypeDescriptor.toBackendIndexTypes(indexTypes));
649      }
650
651      if (entryLimitValue != index.getIndexEntryLimit())
652      {
653        index.setIndexEntryLimit(entryLimitValue);
654      }
655      index.commit();
656    }
657
658    @Override
659    protected String getCommandLinePath()
660    {
661      return null;
662    }
663
664    @Override
665    protected ArrayList<String> getCommandLineArguments()
666    {
667      return new ArrayList<>();
668    }
669
670    /**
671     * Returns the full command-line path of the dsconfig command-line if we can
672     * provide an equivalent command-line (the server is running).
673     *
674     * @return the full command-line path of the dsconfig command-line if we can
675     *         provide an equivalent command-line (the server is running).
676     */
677    private String getConfigCommandLineName()
678    {
679      if (isServerRunning() && isModified())
680      {
681        return getCommandLinePath("dsconfig");
682      }
683      else
684      {
685        return null;
686      }
687    }
688
689    @Override
690    public void runTask()
691    {
692      state = State.RUNNING;
693      lastException = null;
694
695      try
696      {
697        updateConfiguration();
698        modifiedIndex = new IndexDescriptor(attributeName,
699            indexToModify.getAttributeType(),
700            indexToModify.getBackend(),
701            indexTypes,
702            entryLimitValue);
703        getInfo().registerModifiedIndex(modifiedIndex);
704        state = State.FINISHED_SUCCESSFULLY;
705      }
706      catch (Throwable t)
707      {
708        lastException = t;
709        state = State.FINISHED_WITH_ERROR;
710      }
711    }
712
713    @Override
714    public void postOperation()
715    {
716      if (lastException == null && state == State.FINISHED_SUCCESSFULLY)
717      {
718        rebuildIndexIfNecessary(modifiedIndex, getProgressDialog());
719      }
720    }
721
722    private List<String> getDSConfigCommandLineArguments()
723    {
724      List<String> args = new ArrayList<>();
725      args.add("set-backend-index-prop");
726      args.add("--backend-name");
727      args.add(backendName);
728
729      args.add("--index-name");
730      args.add(attributeName);
731
732      if (!indexTypes.equals(indexToModify.getTypes()))
733      {
734        // To add
735        Set<IndexTypeDescriptor> toAdd = new TreeSet<>();
736        for (IndexTypeDescriptor newType : indexTypes)
737        {
738          if (!indexToModify.getTypes().contains(newType))
739          {
740            toAdd.add(newType);
741          }
742        }
743        // To delete
744        Set<IndexTypeDescriptor> toDelete = new TreeSet<>();
745        for (IndexTypeDescriptor oldType : indexToModify.getTypes())
746        {
747          if (!indexTypes.contains(oldType))
748          {
749            toDelete.add(oldType);
750          }
751        }
752        for (IndexTypeDescriptor newType : toDelete)
753        {
754          args.add("--remove");
755          args.add("index-type:" + newType);
756        }
757        for (IndexTypeDescriptor newType : toAdd)
758        {
759          args.add("--add");
760          args.add("index-type:" + newType.toBackendIndexType());
761        }
762      }
763      if (entryLimitValue != indexToModify.getEntryLimit())
764      {
765        args.add("--set");
766        args.add("index-entry-limit:"+entryLimitValue);
767      }
768      args.addAll(getConnectionCommandLineArguments());
769      args.add("--no-prompt");
770      return args;
771    }
772  }
773}