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