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