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