001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2008-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2014-2015 ForgeRock AS
026 */
027package org.opends.guitools.controlpanel.ui;
028
029import static org.opends.messages.AdminToolMessages.*;
030
031import java.awt.Component;
032import java.awt.GridBagConstraints;
033import java.awt.event.ItemEvent;
034import java.awt.event.ItemListener;
035import java.util.ArrayList;
036import java.util.Collection;
037import java.util.HashSet;
038import java.util.LinkedHashSet;
039import java.util.List;
040import java.util.Set;
041import java.util.SortedSet;
042import java.util.TreeSet;
043
044import javax.naming.ldap.InitialLdapContext;
045import javax.swing.DefaultComboBoxModel;
046import javax.swing.JCheckBox;
047import javax.swing.SwingUtilities;
048
049import org.forgerock.i18n.LocalizableMessage;
050import org.opends.guitools.controlpanel.datamodel.BackendDescriptor;
051import org.opends.guitools.controlpanel.datamodel.CategorizedComboBoxElement;
052import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
053import org.opends.guitools.controlpanel.datamodel.IndexDescriptor;
054import org.opends.guitools.controlpanel.datamodel.IndexTypeDescriptor;
055import org.opends.guitools.controlpanel.datamodel.ServerDescriptor;
056import org.opends.guitools.controlpanel.event.ConfigurationChangeEvent;
057import org.opends.guitools.controlpanel.task.Task;
058import org.opends.guitools.controlpanel.util.ConfigReader;
059import org.opends.guitools.controlpanel.util.Utilities;
060import org.opends.server.admin.PropertyException;
061import org.opends.server.admin.client.ManagementContext;
062import org.opends.server.admin.client.ldap.JNDIDirContextAdaptor;
063import org.opends.server.admin.client.ldap.LDAPManagementContext;
064import org.opends.server.admin.std.client.BackendCfgClient;
065import org.opends.server.admin.std.client.BackendIndexCfgClient;
066import org.opends.server.admin.std.client.LocalDBBackendCfgClient;
067import org.opends.server.admin.std.client.LocalDBIndexCfgClient;
068import org.opends.server.admin.std.client.PluggableBackendCfgClient;
069import org.opends.server.admin.std.meta.BackendIndexCfgDefn;
070import org.opends.server.admin.std.meta.LocalDBIndexCfgDefn;
071import org.opends.server.backends.jeb.RemoveOnceLocalDBBackendIsPluggable;
072import org.opends.server.core.DirectoryServer;
073import org.opends.server.types.AttributeType;
074import org.opends.server.types.DN;
075import org.opends.server.types.OpenDsException;
076import org.opends.server.types.Schema;
077
078/**
079 * Panel that appears when the user defines a new index.
080 */
081public class NewIndexPanel extends AbstractIndexPanel
082{
083  private static final long serialVersionUID = -3516011638125862137L;
084
085  private final Component relativeComponent;
086  private Schema schema;
087  private IndexDescriptor newIndex;
088
089  /**
090   * Constructor of the panel.
091   *
092   * @param backendName
093   *          the backend where the index will be created.
094   * @param relativeComponent
095   *          the component relative to which the dialog containing this panel
096   *          will be centered.
097   */
098  public NewIndexPanel(final String backendName, final Component relativeComponent)
099  {
100    super();
101    this.backendName.setText(backendName);
102    this.relativeComponent = relativeComponent;
103    createLayout();
104  }
105
106  @Override
107  public LocalizableMessage getTitle()
108  {
109    return INFO_CTRL_PANEL_NEW_INDEX_TITLE.get();
110  }
111
112  @Override
113  public Component getPreferredFocusComponent()
114  {
115    return attributes;
116  }
117
118  /**
119   * Updates the contents of the panel with the provided backend.
120   *
121   * @param backend
122   *          the backend where the index will be created.
123   */
124  public void update(final BackendDescriptor backend)
125  {
126    backendName.setText(backend.getBackendID());
127  }
128
129  @Override
130  public void configurationChanged(final ConfigurationChangeEvent ev)
131  {
132    final ServerDescriptor desc = ev.getNewDescriptor();
133
134    Schema s = desc.getSchema();
135    final boolean[] repack = { false };
136    final boolean[] error = { false };
137    if (s != null)
138    {
139      schema = s;
140      repack[0] = attributes.getItemCount() == 0;
141      LinkedHashSet<CategorizedComboBoxElement> newElements = new LinkedHashSet<>();
142
143      BackendDescriptor backend = getBackendByID(backendName.getText());
144
145      TreeSet<String> standardAttrNames = new TreeSet<>();
146      TreeSet<String> configurationAttrNames = new TreeSet<>();
147      TreeSet<String> customAttrNames = new TreeSet<>();
148      for (AttributeType attr : schema.getAttributeTypes().values())
149      {
150        String name = attr.getPrimaryName();
151        if (!indexExists(backend, name))
152        {
153          if (Utilities.isStandard(attr))
154          {
155            standardAttrNames.add(name);
156          }
157          else if (Utilities.isConfiguration(attr))
158          {
159            configurationAttrNames.add(name);
160          }
161          else
162          {
163            customAttrNames.add(name);
164          }
165        }
166      }
167      if (!customAttrNames.isEmpty())
168      {
169        newElements.add(new CategorizedComboBoxElement(CUSTOM_ATTRIBUTES, CategorizedComboBoxElement.Type.CATEGORY));
170        for (String attrName : customAttrNames)
171        {
172          newElements.add(new CategorizedComboBoxElement(attrName, CategorizedComboBoxElement.Type.REGULAR));
173        }
174      }
175      if (!standardAttrNames.isEmpty())
176      {
177        newElements.add(new CategorizedComboBoxElement(STANDARD_ATTRIBUTES, CategorizedComboBoxElement.Type.CATEGORY));
178        for (String attrName : standardAttrNames)
179        {
180          newElements.add(new CategorizedComboBoxElement(attrName, CategorizedComboBoxElement.Type.REGULAR));
181        }
182      }
183      DefaultComboBoxModel model = (DefaultComboBoxModel) attributes.getModel();
184      updateComboBoxModel(newElements, model);
185    }
186    else
187    {
188      updateErrorPane(errorPane, ERR_CTRL_PANEL_SCHEMA_NOT_FOUND_SUMMARY.get(), ColorAndFontConstants.errorTitleFont,
189          ERR_CTRL_PANEL_SCHEMA_NOT_FOUND_DETAILS.get(), ColorAndFontConstants.defaultFont);
190      repack[0] = true;
191      error[0] = true;
192    }
193
194    SwingUtilities.invokeLater(new Runnable()
195    {
196      @Override
197      public void run()
198      {
199        setEnabledOK(!error[0]);
200        errorPane.setVisible(error[0]);
201        if (repack[0])
202        {
203          packParentDialog();
204          if (relativeComponent != null)
205          {
206            Utilities.centerGoldenMean(Utilities.getParentDialog(NewIndexPanel.this), relativeComponent);
207          }
208        }
209      }
210    });
211    if (!error[0])
212    {
213      updateErrorPaneAndOKButtonIfAuthRequired(desc, isLocal()
214          ? INFO_CTRL_PANEL_AUTHENTICATION_REQUIRED_FOR_NEW_INDEX.get()
215          : INFO_CTRL_PANEL_CANNOT_CONNECT_TO_REMOTE_DETAILS.get(desc.getHostname()));
216    }
217  }
218
219  private boolean indexExists(BackendDescriptor backend, String indexName)
220  {
221    if (backend != null)
222    {
223      for (IndexDescriptor index : backend.getIndexes())
224      {
225        if (index.getName().equalsIgnoreCase(indexName))
226        {
227          return true;
228        }
229      }
230    }
231    return false;
232  }
233
234  private BackendDescriptor getBackendByID(String backendID)
235  {
236    for (BackendDescriptor b : getInfo().getServerDescriptor().getBackends())
237    {
238      if (b.getBackendID().equalsIgnoreCase(backendID))
239      {
240        return b;
241      }
242    }
243    return null;
244  }
245
246  @Override
247  public void okClicked()
248  {
249    setPrimaryValid(lAttribute);
250    setPrimaryValid(lEntryLimit);
251    setPrimaryValid(lType);
252    List<LocalizableMessage> errors = new ArrayList<>();
253    String attrName = getAttributeName();
254    if (attrName == null)
255    {
256      errors.add(ERR_INFO_CTRL_ATTRIBUTE_NAME_REQUIRED.get());
257      setPrimaryInvalid(lAttribute);
258    }
259
260    String v = entryLimit.getText();
261    try
262    {
263      int n = Integer.parseInt(v);
264      if (n < MIN_ENTRY_LIMIT || MAX_ENTRY_LIMIT < n)
265      {
266        errors.add(ERR_INFO_CTRL_PANEL_ENTRY_LIMIT_NOT_VALID.get(MIN_ENTRY_LIMIT, MAX_ENTRY_LIMIT));
267        setPrimaryInvalid(lEntryLimit);
268      }
269    }
270    catch (Throwable t)
271    {
272      errors.add(ERR_INFO_CTRL_PANEL_ENTRY_LIMIT_NOT_VALID.get(MIN_ENTRY_LIMIT, MAX_ENTRY_LIMIT));
273      setPrimaryInvalid(lEntryLimit);
274    }
275
276    if (!isSomethingSelected())
277    {
278      errors.add(ERR_INFO_ONE_INDEX_TYPE_MUST_BE_SELECTED.get());
279      setPrimaryInvalid(lType);
280    }
281    ProgressDialog dlg = new ProgressDialog(
282        Utilities.createFrame(), Utilities.getParentDialog(this), INFO_CTRL_PANEL_NEW_INDEX_TITLE.get(), getInfo());
283    NewIndexTask newTask = new NewIndexTask(getInfo(), dlg);
284    for (Task task : getInfo().getTasks())
285    {
286      task.canLaunch(newTask, errors);
287    }
288    if (errors.isEmpty())
289    {
290      launchOperation(newTask, INFO_CTRL_PANEL_CREATING_NEW_INDEX_SUMMARY.get(attrName),
291          INFO_CTRL_PANEL_CREATING_NEW_INDEX_SUCCESSFUL_SUMMARY.get(),
292          INFO_CTRL_PANEL_CREATING_NEW_INDEX_SUCCESSFUL_DETAILS.get(attrName),
293          ERR_CTRL_PANEL_CREATING_NEW_INDEX_ERROR_SUMMARY.get(),
294          ERR_CTRL_PANEL_CREATING_NEW_INDEX_ERROR_DETAILS.get(),
295          null, dlg);
296      dlg.setVisible(true);
297      Utilities.getParentDialog(this).setVisible(false);
298    }
299    else
300    {
301      displayErrorDialog(errors);
302    }
303  }
304
305  private boolean isSomethingSelected()
306  {
307    for (JCheckBox type : types)
308    {
309      boolean somethingSelected = type.isSelected() && type.isVisible();
310      if (somethingSelected)
311      {
312        return true;
313      }
314    }
315    return false;
316  }
317
318  private String getAttributeName()
319  {
320    CategorizedComboBoxElement o = (CategorizedComboBoxElement) attributes.getSelectedItem();
321    return o != null ? o.getValue().toString() : null;
322  }
323
324  /** Creates the layout of the panel (but the contents are not populated here). */
325  private void createLayout()
326  {
327    GridBagConstraints gbc = new GridBagConstraints();
328    createBasicLayout(this, gbc, false);
329
330    attributes.addItemListener(new ItemListener()
331    {
332      @Override
333      public void itemStateChanged(final ItemEvent ev)
334      {
335        String n = getAttributeName();
336        AttributeType attr = null;
337        if (n != null)
338        {
339          attr = schema.getAttributeType(n.toLowerCase());
340        }
341        repopulateTypesPanel(attr);
342      }
343    });
344    entryLimit.setText(String.valueOf(DEFAULT_ENTRY_LIMIT));
345  }
346
347  /** The task in charge of creating the index. */
348  private class NewIndexTask extends Task
349  {
350    private final Set<String> backendSet = new HashSet<>();
351    private final String attributeName;
352    private final int entryLimitValue;
353    private final SortedSet<IndexTypeDescriptor> indexTypes;
354
355    /**
356     * The constructor of the task.
357     *
358     * @param info
359     *          the control panel info.
360     * @param dlg
361     *          the progress dialog that shows the progress of the task.
362     */
363    public NewIndexTask(final ControlPanelInfo info, final ProgressDialog dlg)
364    {
365      super(info, dlg);
366      backendSet.add(backendName.getText());
367      attributeName = getAttributeName();
368      entryLimitValue = Integer.parseInt(entryLimit.getText());
369      indexTypes = getTypes();
370    }
371
372    @Override
373    public Type getType()
374    {
375      return Type.NEW_INDEX;
376    }
377
378    @Override
379    public Set<String> getBackends()
380    {
381      return backendSet;
382    }
383
384    @Override
385    public LocalizableMessage getTaskDescription()
386    {
387      return INFO_CTRL_PANEL_NEW_INDEX_TASK_DESCRIPTION.get(attributeName, backendName.getText());
388    }
389
390    @Override
391    public boolean canLaunch(final Task taskToBeLaunched, final Collection<LocalizableMessage> incompatibilityReasons)
392    {
393      boolean canLaunch = true;
394      if (state == State.RUNNING && runningOnSameServer(taskToBeLaunched))
395      {
396        // All the operations are incompatible if they apply to this
397        // backend for safety.  This is a short operation so the limitation
398        // has not a lot of impact.
399        Set<String> backends = new TreeSet<>(taskToBeLaunched.getBackends());
400        backends.retainAll(getBackends());
401        if (!backends.isEmpty())
402        {
403          incompatibilityReasons.add(getIncompatibilityMessage(this, taskToBeLaunched));
404          canLaunch = false;
405        }
406      }
407      return canLaunch;
408    }
409
410    private void updateConfiguration() throws OpenDsException
411    {
412      boolean configHandlerUpdated = false;
413      try
414      {
415        if (!isServerRunning())
416        {
417          configHandlerUpdated = true;
418          getInfo().stopPooling();
419          if (getInfo().mustDeregisterConfig())
420          {
421            DirectoryServer.deregisterBaseDN(DN.valueOf("cn=config"));
422          }
423          DirectoryServer.getInstance().initializeConfiguration(
424              org.opends.server.extensions.ConfigFileHandler.class.getName(), ConfigReader.configFile);
425          getInfo().setMustDeregisterConfig(true);
426        }
427        else
428        {
429          SwingUtilities.invokeLater(new Runnable()
430          {
431            @Override
432            public void run()
433            {
434              List<String> args = getObfuscatedCommandLineArguments(getDSConfigCommandLineArguments());
435              args.removeAll(getConfigCommandLineArguments());
436              printEquivalentCommandLine(
437                  getConfigCommandLineName(), args, INFO_CTRL_PANEL_EQUIVALENT_CMD_TO_CREATE_INDEX.get());
438            }
439          });
440        }
441        SwingUtilities.invokeLater(new Runnable()
442        {
443          @Override
444          public void run()
445          {
446            getProgressDialog().appendProgressHtml(Utilities.getProgressWithPoints(
447                INFO_CTRL_PANEL_CREATING_NEW_INDEX_PROGRESS.get(attributeName), ColorAndFontConstants.progressFont));
448          }
449        });
450
451        if (isServerRunning())
452        {
453          createIndexOnline(getInfo().getDirContext());
454        }
455        else
456        {
457          createIndexOffline(backendName.getText(), attributeName, indexTypes, entryLimitValue);
458        }
459        SwingUtilities.invokeLater(new Runnable()
460        {
461          @Override
462          public void run()
463          {
464            getProgressDialog().appendProgressHtml(Utilities.getProgressDone(ColorAndFontConstants.progressFont));
465          }
466        });
467      }
468      finally
469      {
470        if (configHandlerUpdated)
471        {
472          DirectoryServer.getInstance().initializeConfiguration(ConfigReader.configClassName, ConfigReader.configFile);
473          getInfo().startPooling();
474        }
475      }
476    }
477
478    private void createIndexOnline(final InitialLdapContext ctx) throws OpenDsException
479    {
480      final ManagementContext mCtx = LDAPManagementContext.createFromContext(JNDIDirContextAdaptor.adapt(ctx));
481      final BackendCfgClient backend = mCtx.getRootConfiguration().getBackend(backendName.getText());
482      if (backend instanceof LocalDBBackendCfgClient)
483      {
484        createLocalDBIndexOnline((LocalDBBackendCfgClient) backend);
485        return;
486      }
487      createBackendIndexOnline((PluggableBackendCfgClient) backend);
488    }
489
490    private void createBackendIndexOnline(final PluggableBackendCfgClient backend) throws OpenDsException
491    {
492      final List<PropertyException> exceptions = new ArrayList<>();
493      final BackendIndexCfgClient index = backend.createBackendIndex(
494          BackendIndexCfgDefn.getInstance(), attributeName, exceptions);
495      index.setIndexType(IndexTypeDescriptor.toBackendIndexTypes(indexTypes));
496      if (entryLimitValue != index.getIndexEntryLimit())
497      {
498        index.setIndexEntryLimit(entryLimitValue);
499      }
500      index.commit();
501      Utilities.throwFirstFrom(exceptions);
502    }
503
504    @RemoveOnceLocalDBBackendIsPluggable
505    private void createLocalDBIndexOnline(final LocalDBBackendCfgClient backend) throws OpenDsException
506    {
507      final List<PropertyException> exceptions = new ArrayList<>();
508      final LocalDBIndexCfgClient index = backend.createLocalDBIndex(
509          LocalDBIndexCfgDefn.getInstance(), attributeName, exceptions);
510      index.setIndexType(IndexTypeDescriptor.toLocalDBIndexTypes(indexTypes));
511      if (entryLimitValue != index.getIndexEntryLimit())
512      {
513        index.setIndexEntryLimit(entryLimitValue);
514      }
515      index.commit();
516      Utilities.throwFirstFrom(exceptions);
517    }
518
519    @Override
520    protected String getCommandLinePath()
521    {
522      return null;
523    }
524
525    @Override
526    protected List<String> getCommandLineArguments()
527    {
528      return new ArrayList<>();
529    }
530
531    private String getConfigCommandLineName()
532    {
533      if (isServerRunning())
534      {
535        return getCommandLinePath("dsconfig");
536      }
537      return null;
538    }
539
540    @Override
541    public void runTask()
542    {
543      state = State.RUNNING;
544      lastException = null;
545
546      try
547      {
548        updateConfiguration();
549        for (BackendDescriptor backend : getInfo().getServerDescriptor().getBackends())
550        {
551          if (backend.getBackendID().equalsIgnoreCase(backendName.getText()))
552          {
553            newIndex = new IndexDescriptor(attributeName,
554                schema.getAttributeType(attributeName.toLowerCase()), backend, indexTypes, entryLimitValue);
555            getInfo().registerModifiedIndex(newIndex);
556            notifyConfigurationElementCreated(newIndex);
557            break;
558          }
559        }
560        state = State.FINISHED_SUCCESSFULLY;
561      }
562      catch (Throwable t)
563      {
564        lastException = t;
565        state = State.FINISHED_WITH_ERROR;
566      }
567    }
568
569    @Override
570    public void postOperation()
571    {
572      if (lastException == null && state == State.FINISHED_SUCCESSFULLY && newIndex != null)
573      {
574        rebuildIndexIfNecessary(newIndex, getProgressDialog());
575      }
576    }
577
578    private ArrayList<String> getDSConfigCommandLineArguments()
579    {
580      ArrayList<String> args = new ArrayList<>();
581      args.add("create-local-db-index");
582      args.add("--backend-name");
583      args.add(backendName.getText());
584      args.add("--type");
585      args.add("generic");
586
587      args.add("--index-name");
588      args.add(attributeName);
589
590      for (IndexTypeDescriptor type : indexTypes)
591      {
592        args.add("--set");
593        args.add("index-type:" + type.toLocalDBIndexType());
594      }
595      args.add("--set");
596      args.add("index-entry-limit:" + entryLimitValue);
597      args.addAll(getConnectionCommandLineArguments());
598      args.add(getNoPropertiesFileArgument());
599      args.add("--no-prompt");
600      return args;
601    }
602  }
603}