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-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.guitools.controlpanel.ui;
018
019import static org.opends.messages.AdminToolMessages.*;
020import static org.opends.server.types.CommonSchemaElements.*;
021import static org.opends.server.util.CollectionUtils.*;
022import static org.opends.server.util.SchemaUtils.*;
023
024import java.awt.Component;
025import java.awt.Container;
026import java.awt.GridBagConstraints;
027import java.awt.GridBagLayout;
028import java.awt.Insets;
029import java.awt.event.ActionEvent;
030import java.awt.event.ActionListener;
031import java.io.File;
032import java.util.ArrayList;
033import java.util.Collection;
034import java.util.Collections;
035import java.util.Comparator;
036import java.util.HashMap;
037import java.util.HashSet;
038import java.util.LinkedHashSet;
039import java.util.List;
040import java.util.Map;
041import java.util.Set;
042
043import javax.swing.DefaultComboBoxModel;
044import javax.swing.JButton;
045import javax.swing.JCheckBox;
046import javax.swing.JComboBox;
047import javax.swing.JLabel;
048import javax.swing.JList;
049import javax.swing.JPanel;
050import javax.swing.JScrollPane;
051import javax.swing.JTextField;
052import javax.swing.ListCellRenderer;
053import javax.swing.SwingUtilities;
054import javax.swing.event.ChangeEvent;
055import javax.swing.event.ChangeListener;
056import javax.swing.event.DocumentEvent;
057import javax.swing.event.DocumentListener;
058import javax.swing.event.ListDataEvent;
059import javax.swing.event.ListDataListener;
060
061import org.forgerock.i18n.LocalizableMessage;
062import org.forgerock.i18n.LocalizableMessageBuilder;
063import org.forgerock.opendj.ldap.schema.AttributeType;
064import org.forgerock.opendj.ldap.schema.ObjectClass;
065import org.forgerock.opendj.ldap.schema.ObjectClassType;
066import org.forgerock.opendj.ldap.schema.SchemaBuilder;
067import org.opends.guitools.controlpanel.datamodel.ServerDescriptor;
068import org.opends.guitools.controlpanel.datamodel.SomeSchemaElement;
069import org.opends.guitools.controlpanel.datamodel.SortableListModel;
070import org.opends.guitools.controlpanel.event.ConfigurationChangeEvent;
071import org.opends.guitools.controlpanel.event.ConfigurationElementCreatedListener;
072import org.opends.guitools.controlpanel.event.ScrollPaneBorderListener;
073import org.opends.guitools.controlpanel.event.SuperiorObjectClassesChangedEvent;
074import org.opends.guitools.controlpanel.event.SuperiorObjectClassesChangedListener;
075import org.opends.guitools.controlpanel.task.DeleteSchemaElementsTask;
076import org.opends.guitools.controlpanel.task.ModifyObjectClassTask;
077import org.opends.guitools.controlpanel.task.Task;
078import org.opends.guitools.controlpanel.ui.components.BasicExpander;
079import org.opends.guitools.controlpanel.ui.components.DoubleAddRemovePanel;
080import org.opends.guitools.controlpanel.ui.components.SuperiorObjectClassesEditor;
081import org.opends.guitools.controlpanel.ui.components.TitlePanel;
082import org.opends.guitools.controlpanel.ui.renderer.SchemaElementComboBoxCellRenderer;
083import org.opends.guitools.controlpanel.util.Utilities;
084import org.opends.server.types.Schema;
085import org.opends.server.util.ServerConstants;
086import org.opends.server.util.StaticUtils;
087
088/** The panel that displays a custom object class definition. */
089public class CustomObjectClassPanel extends SchemaElementPanel
090{
091  private static final long serialVersionUID = 2105520588901380L;
092  private JButton delete;
093  private JButton saveChanges;
094  private ObjectClass objectClass;
095  private String ocName;
096  private ScrollPaneBorderListener scrollListener;
097
098  private TitlePanel titlePanel = new TitlePanel(LocalizableMessage.EMPTY, LocalizableMessage.EMPTY);
099  private JLabel lName = Utilities.createPrimaryLabel(
100      INFO_CTRL_PANEL_OBJECTCLASS_NAME_LABEL.get());
101  private JLabel lSuperior = Utilities.createPrimaryLabel(
102      INFO_CTRL_PANEL_OBJECTCLASS_PARENT_LABEL.get());
103  private JLabel lOID = Utilities.createPrimaryLabel(
104      INFO_CTRL_PANEL_OBJECTCLASS_OID_LABEL.get());
105  private JLabel lAliases = Utilities.createPrimaryLabel(
106      INFO_CTRL_PANEL_OBJECTCLASS_ALIASES_LABEL.get());
107  private JLabel lOrigin = Utilities.createPrimaryLabel(
108      INFO_CTRL_PANEL_OBJECTCLASS_ORIGIN_LABEL.get());
109  private JLabel lFile = Utilities.createPrimaryLabel(
110      INFO_CTRL_PANEL_OBJECTCLASS_FILE_LABEL.get());
111  private JTextField aliases = Utilities.createLongTextField();
112  private JLabel lDescription = Utilities.createPrimaryLabel(
113      INFO_CTRL_PANEL_OBJECTCLASS_DESCRIPTION_LABEL.get());
114  private JLabel lType = Utilities.createPrimaryLabel(
115      INFO_CTRL_PANEL_OBJECTCLASS_TYPE_LABEL.get());
116  private JLabel lAttributes = Utilities.createPrimaryLabel(
117      INFO_CTRL_PANEL_OBJECTCLASS_ATTRIBUTES_LABEL.get());
118
119  private Set<AttributeType> inheritedOptionalAttributes = new HashSet<>();
120  private Set<AttributeType> inheritedRequiredAttributes = new HashSet<>();
121
122  private JLabel[] labels = {lName, lSuperior, lOID, lAliases, lOrigin, lFile,
123      lDescription, lType, lAttributes
124  };
125
126  private JTextField name = Utilities.createMediumTextField();
127  private SuperiorObjectClassesEditor superiors =
128    new SuperiorObjectClassesEditor();
129  private JComboBox type = Utilities.createComboBox();
130  private JTextField oid = Utilities.createMediumTextField();
131  private JTextField description = Utilities.createLongTextField();
132  private JTextField origin = Utilities.createLongTextField();
133  private JTextField file = Utilities.createLongTextField();
134  private JCheckBox obsolete = Utilities.createCheckBox(
135      INFO_CTRL_PANEL_OBJECTCLASS_OBSOLETE_LABEL.get());
136  private DoubleAddRemovePanel<AttributeType> attributes;
137
138  private Schema schema;
139  private Set<String> lastAliases = new LinkedHashSet<>();
140
141  private boolean ignoreChangeEvents;
142
143  /** Default constructor of the panel. */
144  public CustomObjectClassPanel()
145  {
146    super();
147    createLayout();
148  }
149
150  @Override
151  public LocalizableMessage getTitle()
152  {
153    return INFO_CTRL_PANEL_CUSTOM_OBJECTCLASS_TITLE.get();
154  }
155
156  /** Creates the layout of the panel (but the contents are not populated here). */
157  private void createLayout()
158  {
159    JPanel p = new JPanel(new GridBagLayout());
160    GridBagConstraints gbc = new GridBagConstraints();
161    p.setOpaque(false);
162    p.setBorder(PANEL_BORDER);
163    createBasicLayout(p, gbc);
164    gbc = new GridBagConstraints();
165    gbc.weightx = 1.0;
166    gbc.weighty = 1.0;
167    gbc.gridwidth = 2;
168    gbc.fill = GridBagConstraints.BOTH;
169    gbc.gridx = 0;
170    gbc.gridy = 0;
171    JScrollPane scroll = Utilities.createBorderLessScrollBar(p);
172    scrollListener =
173      ScrollPaneBorderListener.createBottomBorderListener(scroll);
174    add(scroll, gbc);
175
176    gbc.gridy ++;
177    gbc.weighty = 0.0;
178    gbc.anchor = GridBagConstraints.WEST;
179    gbc.fill = GridBagConstraints.NONE;
180    gbc.insets = new Insets(10, 10, 10, 10);
181    gbc.gridwidth = 1;
182    delete = Utilities.createButton(
183        INFO_CTRL_PANEL_DELETE_OBJECTCLASS_BUTTON.get());
184    delete.setOpaque(false);
185    add(delete, gbc);
186    delete.addActionListener(new ActionListener()
187    {
188      @Override
189      public void actionPerformed(ActionEvent ev)
190      {
191        deleteObjectclass();
192      }
193    });
194
195    gbc.anchor = GridBagConstraints.EAST;
196    gbc.gridx ++;
197    saveChanges =
198      Utilities.createButton(INFO_CTRL_PANEL_SAVE_CHANGES_LABEL.get());
199    saveChanges.setOpaque(false);
200    add(saveChanges, gbc);
201    saveChanges.addActionListener(new ActionListener()
202    {
203      @Override
204      public void actionPerformed(ActionEvent ev)
205      {
206        ArrayList<LocalizableMessage> errors = new ArrayList<>();
207        saveChanges(errors);
208      }
209    });
210  }
211
212  /**
213   * Creates the basic layout of the panel.
214   * @param c the container where all the components will be layed out.
215   * @param gbc the grid bag constraints.
216   */
217  private void createBasicLayout(Container c, GridBagConstraints gbc)
218  {
219    SuperiorObjectClassesChangedListener listener =
220      new SuperiorObjectClassesChangedListener()
221    {
222      @Override
223      public void parentObjectClassesChanged(
224          SuperiorObjectClassesChangedEvent ev)
225      {
226        if (ignoreChangeEvents)
227        {
228          return;
229        }
230        updateAttributesWithParent(true);
231        checkEnableSaveChanges();
232        if (ev.getNewObjectClasses().size() > 1)
233        {
234          lSuperior.setText(
235              INFO_CTRL_PANEL_OBJECTCLASS_PARENTS_LABEL.get().toString());
236        }
237        else
238        {
239          lSuperior.setText(
240              INFO_CTRL_PANEL_OBJECTCLASS_PARENT_LABEL.get().toString());
241        }
242      }
243    };
244    superiors.addParentObjectClassesChangedListener(listener);
245
246    DefaultComboBoxModel model = new DefaultComboBoxModel();
247    for (ObjectClassType t : ObjectClassType.values())
248    {
249      model.addElement(t);
250    }
251    type.setModel(model);
252    type.setSelectedItem(ObjectClassType.STRUCTURAL);
253    SchemaElementComboBoxCellRenderer renderer = new
254    SchemaElementComboBoxCellRenderer(type);
255    type.setRenderer(renderer);
256
257    attributes = new DoubleAddRemovePanel<>(0, AttributeType.class);
258    Comparator<AttributeType> comparator = new Comparator<AttributeType>()
259    {
260      @Override
261      public int compare(AttributeType attr1, AttributeType attr2)
262      {
263        return attr1.getNameOrOID().toLowerCase().compareTo(
264            attr2.getNameOrOID().toLowerCase());
265      }
266    };
267    attributes.getAvailableListModel().setComparator(comparator);
268    attributes.getSelectedListModel1().setComparator(comparator);
269    attributes.getSelectedListModel2().setComparator(comparator);
270
271    gbc.gridy = 0;
272    gbc.gridwidth = 2;
273    addErrorPane(c, gbc);
274    gbc.gridy ++;
275
276    gbc.anchor = GridBagConstraints.WEST;
277    titlePanel.setTitle(INFO_CTRL_PANEL_OBJECTCLASS_DETAILS.get());
278    gbc.fill = GridBagConstraints.NONE;
279    gbc.insets.top = 5;
280    gbc.insets.bottom = 7;
281    c.add(titlePanel, gbc);
282
283    gbc.insets.bottom = 0;
284    gbc.insets.top = 8;
285    gbc.gridy ++;
286    gbc.gridwidth = 1;
287    gbc.fill = GridBagConstraints.HORIZONTAL;
288
289    Component[] basicComps = {name, oid, description, superiors};
290    JLabel[] basicLabels = {lName, lOID, lDescription, lSuperior};
291    JLabel[] basicInlineHelp = new JLabel[] {null, null, null, null};
292    add(basicLabels, basicComps, basicInlineHelp, c, gbc);
293
294    gbc.gridx = 0;
295    gbc.weightx = 0.0;
296    gbc.insets.left = 0;
297    gbc.fill = GridBagConstraints.HORIZONTAL;
298    gbc.anchor = GridBagConstraints.NORTHWEST;
299    c.add(lAttributes, gbc);
300
301    gbc.gridx ++;
302    gbc.fill = GridBagConstraints.BOTH;
303    gbc.weightx = 1.0;
304    gbc.weighty = 1.0;
305    gbc.insets.left = 10;
306    c.add(attributes, gbc);
307    attributes.getAvailableLabel().setText(
308        INFO_CTRL_PANEL_ADDREMOVE_AVAILABLE_ATTRIBUTES.get().toString());
309    attributes.getSelectedLabel1().setText(
310        INFO_CTRL_PANEL_ADDREMOVE_REQUIRED_ATTRIBUTES.get().toString());
311    attributes.getSelectedLabel2().setText(
312        INFO_CTRL_PANEL_ADDREMOVE_OPTIONAL_ATTRIBUTES.get().toString());
313    AttributeTypeCellRenderer listRenderer = new AttributeTypeCellRenderer();
314    attributes.getAvailableList().setCellRenderer(listRenderer);
315    attributes.getSelectedList1().setCellRenderer(listRenderer);
316    attributes.getSelectedList2().setCellRenderer(listRenderer);
317
318    gbc.gridy ++;
319    gbc.weighty = 0.0;
320    gbc.insets.top = 3;
321    JLabel explanation = Utilities.createInlineHelpLabel(
322        INFO_CTRL_PANEL_INHERITED_ATTRIBUTES_HELP.get());
323    gbc.insets.top = 3;
324    c.add(explanation, gbc);
325
326    final BasicExpander expander = new BasicExpander(
327        INFO_CTRL_PANEL_EXTRA_OPTIONS_EXPANDER.get());
328
329    obsolete.setText("Obsolete");
330
331    Component[] comps = {aliases, origin, file, type, obsolete};
332    JLabel[] labels = {lAliases, lOrigin, lFile, lType, null};
333    JLabel[] inlineHelps = {
334        Utilities.createInlineHelpLabel(
335            INFO_CTRL_PANEL_SEPARATED_WITH_COMMAS_HELP.get()), null,
336        Utilities.createInlineHelpLabel(
337            INFO_CTRL_PANEL_SCHEMA_FILE_OBJECTCLASS_HELP.get(File.separator)),
338            null, null};
339    gbc.gridwidth = 2;
340    gbc.gridx = 0;
341    gbc.weighty = 0.0;
342    gbc.insets.left = 0;
343    gbc.gridy ++;
344    c.add(expander, gbc);
345    final JPanel p = new JPanel(new GridBagLayout());
346    gbc.insets.left = 15;
347    gbc.gridy ++;
348    c.add(p, gbc);
349    gbc.gridy ++;
350    p.setOpaque(false);
351
352    GridBagConstraints gbc1 = new GridBagConstraints();
353    gbc1.fill = GridBagConstraints.HORIZONTAL;
354    gbc1.gridy = 0;
355
356    add(labels, comps, inlineHelps, p, gbc1);
357    ChangeListener changeListener = new ChangeListener()
358    {
359      @Override
360      public void stateChanged(ChangeEvent e)
361      {
362        p.setVisible(expander.isSelected());
363      }
364    };
365    expander.addChangeListener(changeListener);
366    expander.setSelected(false);
367    changeListener.stateChanged(null);
368
369    DocumentListener docListener = new DocumentListener()
370    {
371      @Override
372      public void insertUpdate(DocumentEvent ev)
373      {
374        checkEnableSaveChanges();
375      }
376
377      @Override
378      public void removeUpdate(DocumentEvent ev)
379      {
380        checkEnableSaveChanges();
381      }
382
383      @Override
384      public void changedUpdate(DocumentEvent arg0)
385      {
386        checkEnableSaveChanges();
387      }
388    };
389    JTextField[] tfs = {name, description, oid, aliases, origin, file};
390    for (JTextField tf : tfs)
391    {
392      tf.getDocument().addDocumentListener(docListener);
393    }
394
395    ActionListener actionListener = new ActionListener()
396    {
397      @Override
398      public void actionPerformed(ActionEvent ev)
399      {
400        checkEnableSaveChanges();
401      }
402    };
403
404    type.addActionListener(actionListener);
405
406    ListDataListener dataListener = new ListDataListener()
407    {
408      @Override
409      public void contentsChanged(ListDataEvent e)
410      {
411        checkEnableSaveChanges();
412      }
413      @Override
414      public void intervalAdded(ListDataEvent e)
415      {
416        checkEnableSaveChanges();
417      }
418      @Override
419      public void intervalRemoved(ListDataEvent e)
420      {
421        checkEnableSaveChanges();
422      }
423    };
424    SortableListModel<AttributeType> list1 = attributes.getSelectedListModel1();
425    SortableListModel<AttributeType> list2 = attributes.getSelectedListModel2();
426    list1.addListDataListener(dataListener);
427    list2.addListDataListener(dataListener);
428
429    obsolete.addActionListener(actionListener);
430  }
431
432  /**
433   * Updates the contents of the panel with the provided object class.
434   * @param oc the object class.
435   * @param schema the schema.
436   */
437  public void update(ObjectClass oc, Schema schema)
438  {
439    ignoreChangeEvents = true;
440
441    objectClass = oc;
442    if (oc == null || schema == null)
443    {
444      // Ignore: this is called to get an initial panel size.
445      return;
446    }
447    String n = oc.getNameOrOID();
448    if (n == null)
449    {
450      n = NOT_APPLICABLE.toString();
451    }
452    titlePanel.setDetails(LocalizableMessage.raw(n));
453    name.setText(n);
454
455    SortableListModel<AttributeType> modelRequired = attributes.getSelectedListModel1();
456    SortableListModel<AttributeType> modelAvailable = attributes.getSelectedListModel2();
457    SortableListModel<AttributeType> availableModel = attributes.getAvailableListModel();
458    availableModel.addAll(modelRequired.getData());
459    availableModel.addAll(modelAvailable.getData());
460    modelRequired.clear();
461    modelAvailable.clear();
462
463    superiors.setSelectedSuperiors(oc.getSuperiorClasses());
464    superiors.setObjectClassesToExclude(Collections.singleton(oc));
465    if (oc.getSuperiorClasses().size() > 1)
466    {
467      lSuperior.setText(
468          INFO_CTRL_PANEL_OBJECTCLASS_PARENTS_LABEL.get().toString());
469    }
470    else
471    {
472      lSuperior.setText(
473          INFO_CTRL_PANEL_OBJECTCLASS_PARENT_LABEL.get().toString());
474    }
475
476    updateAttributesWithParent(false);
477
478    for (AttributeType attr : oc.getDeclaredRequiredAttributes())
479    {
480      availableModel.remove(attr);
481      modelRequired.add(attr);
482    }
483    for (AttributeType attr : oc.getDeclaredOptionalAttributes())
484    {
485      availableModel.remove(attr);
486      modelAvailable.add(attr);
487    }
488    notifyAttributesChanged();
489
490    oid.setText(oc.getOID());
491    n = oc.getDescription();
492    if (n == null)
493    {
494      n = "";
495    }
496    description.setText(n);
497
498    Set<String> aliases = getAliases(oc);
499    lastAliases.clear();
500    lastAliases.addAll(aliases);
501    this.aliases.setText(Utilities.getStringFromCollection(aliases, ", "));
502
503    String sOrigin = new SomeSchemaElement(oc).getOrigin();
504    if (sOrigin == null)
505    {
506      sOrigin = "";
507    }
508    origin.setText(sOrigin);
509
510    String sFile = getSchemaFile(oc);
511    if (sFile == null)
512    {
513      sFile = "";
514    }
515    file.setText(sFile);
516
517    type.setSelectedItem(oc.getObjectClassType());
518
519    obsolete.setSelected(oc.isObsolete());
520
521    ocName = objectClass.getNameOrOID();
522    scrollListener.updateBorder();
523    for (JLabel label : labels)
524    {
525      setPrimaryValid(label);
526    }
527    saveChanges.setEnabled(false);
528    ignoreChangeEvents = false;
529  }
530
531  @Override
532  public void configurationChanged(ConfigurationChangeEvent ev)
533  {
534    final ServerDescriptor desc = ev.getNewDescriptor();
535    Schema s = desc.getSchema();
536    final boolean schemaChanged = schemaChanged(s);
537    if (schemaChanged)
538    {
539      schema = s;
540
541      updateErrorPaneIfAuthRequired(desc, isLocal()
542          ? INFO_CTRL_PANEL_AUTHENTICATION_REQUIRED_FOR_OBJECTCLASS_EDIT.get()
543          : INFO_CTRL_PANEL_CANNOT_CONNECT_TO_REMOTE_DETAILS.get(desc.getHostname()));
544    }
545    else if (schema == null)
546    {
547      updateErrorPane(errorPane,
548          ERR_CTRL_PANEL_SCHEMA_NOT_FOUND_SUMMARY.get(),
549          ColorAndFontConstants.errorTitleFont,
550          ERR_CTRL_PANEL_SCHEMA_NOT_FOUND_DETAILS.get(),
551          ColorAndFontConstants.defaultFont);
552    }
553    SwingUtilities.invokeLater(new Runnable()
554    {
555      @Override
556      public void run()
557      {
558        final boolean enabled = !authenticationRequired(desc) && schema != null;
559        delete.setEnabled(enabled);
560        checkEnableSaveChanges();
561        saveChanges.setEnabled(enabled && saveChanges.isEnabled());
562        if (schemaChanged && schema != null)
563        {
564          superiors.setSchema(schema);
565          updateAttributes();
566        }
567      }
568    });
569  }
570
571  private boolean schemaChanged(Schema s)
572  {
573    if (s != null)
574    {
575      return schema == null || !ServerDescriptor.areSchemasEqual(s, schema);
576    }
577    return false;
578  }
579
580  @Override
581  public boolean mustCheckUnsavedChanges()
582  {
583    return saveChanges.isEnabled();
584  }
585
586  @Override
587  public UnsavedChangesDialog.Result checkUnsavedChanges()
588  {
589    UnsavedChangesDialog.Result result;
590    UnsavedChangesDialog unsavedChangesDlg = new UnsavedChangesDialog(
591          Utilities.getParentDialog(this), getInfo());
592    unsavedChangesDlg.setMessage(INFO_CTRL_PANEL_UNSAVED_CHANGES_SUMMARY.get(),
593        INFO_CTRL_PANEL_UNSAVED_OBJECTCLASS_CHANGES_DETAILS.get(
594           objectClass.getNameOrOID()));
595    Utilities.centerGoldenMean(unsavedChangesDlg,
596          Utilities.getParentDialog(this));
597    unsavedChangesDlg.setVisible(true);
598    result = unsavedChangesDlg.getResult();
599    if (result == UnsavedChangesDialog.Result.SAVE)
600    {
601      List<LocalizableMessage> errors = new ArrayList<>();
602      saveChanges(errors);
603      if (!errors.isEmpty())
604      {
605        result = UnsavedChangesDialog.Result.CANCEL;
606      }
607    }
608
609    return result;
610  }
611
612  @Override
613  public Component getPreferredFocusComponent()
614  {
615    return name;
616  }
617
618  @Override
619  public void okClicked()
620  {
621  }
622
623  private void deleteObjectclass()
624  {
625    ArrayList<LocalizableMessage> errors = new ArrayList<>();
626    ProgressDialog dlg = new ProgressDialog(
627        Utilities.createFrame(),
628        Utilities.getParentDialog(this),
629        INFO_CTRL_PANEL_DELETE_OBJECTCLASS_TITLE.get(), getInfo());
630    LinkedHashSet<ObjectClass> ocsToDelete = new LinkedHashSet<>();
631    ocsToDelete.add(objectClass);
632    LinkedHashSet<AttributeType> attrsToDelete = new LinkedHashSet<>(0);
633
634    DeleteSchemaElementsTask newTask = new DeleteSchemaElementsTask(getInfo(),
635        dlg, ocsToDelete, attrsToDelete);
636    for (Task task : getInfo().getTasks())
637    {
638      task.canLaunch(newTask, errors);
639    }
640    Schema schema = getInfo().getServerDescriptor().getSchema();
641    ArrayList<String> childClasses = new ArrayList<>();
642    if (schema != null)
643    {
644      for (ObjectClass o : schema.getObjectClasses())
645      {
646        for (ObjectClass superior : o.getSuperiorClasses())
647        {
648          if (objectClass.equals(superior))
649          {
650            childClasses.add(o.getNameOrOID());
651          }
652        }
653      }
654    }
655    else
656    {
657      errors.add(ERR_CTRL_PANEL_SCHEMA_NOT_FOUND_DETAILS.get());
658    }
659    if (errors.isEmpty())
660    {
661      LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
662
663      if (!childClasses.isEmpty())
664      {
665        mb.append(INFO_OBJECTCLASS_IS_SUPERIOR.get(
666            ocName,
667            Utilities.getStringFromCollection(childClasses, ", ")));
668        mb.append("<br>");
669      }
670      LocalizableMessage confirmationMessage =
671        INFO_CTRL_PANEL_CONFIRMATION_DELETE_OBJECTCLASS_DETAILS.get(
672            ocName);
673      mb.append(confirmationMessage);
674      if (displayConfirmationDialog(
675          INFO_CTRL_PANEL_CONFIRMATION_REQUIRED_SUMMARY.get(),
676          confirmationMessage))
677      {
678        launchOperation(newTask,
679            INFO_CTRL_PANEL_DELETING_OBJECTCLASS_SUMMARY.get(ocName),
680            INFO_CTRL_PANEL_DELETING_OBJECTCLASS_COMPLETE.get(),
681            INFO_CTRL_PANEL_DELETING_OBJECTCLASS_SUCCESSFUL.get(ocName),
682            ERR_CTRL_PANEL_DELETING_OBJECTCLASS_ERROR_SUMMARY.get(),
683            ERR_CTRL_PANEL_DELETING_OBJECTCLASS_ERROR_DETAILS.get(ocName),
684            null,
685            dlg);
686        dlg.setVisible(true);
687      }
688    }
689    else
690    {
691      displayErrorDialog(errors);
692    }
693  }
694
695  private void saveChanges(List<LocalizableMessage> errors)
696  {
697    for (JLabel label : labels)
698    {
699      setPrimaryValid(label);
700    }
701    String n = getObjectClassName();
702    LocalizableMessageBuilder err = new LocalizableMessageBuilder();
703    if (n.length() == 0)
704    {
705      errors.add(ERR_CTRL_PANEL_OBJECTCLASS_NAME_REQUIRED.get());
706      setPrimaryInvalid(lName);
707    }
708    else if (!n.equalsIgnoreCase(objectClass.getNameOrOID()))
709    {
710      if (!StaticUtils.isValidSchemaElement(n, 0, n.length(), err))
711      {
712        errors.add(ERR_CTRL_PANEL_INVALID_OBJECTCLASS_NAME.get(err));
713        setPrimaryInvalid(lName);
714        err = new LocalizableMessageBuilder();
715      }
716      else
717      {
718        LocalizableMessage elementType = NewAttributePanel.getSchemaElementType(n, schema);
719        if (elementType != null)
720        {
721          errors.add(ERR_CTRL_PANEL_OBJECTCLASS_NAME_ALREADY_IN_USE.get(n, elementType));
722          setPrimaryInvalid(lName);
723        }
724      }
725    }
726    n = oid.getText().trim();
727    if (n.length() > 0 && !n.equalsIgnoreCase(objectClass.getOID()))
728    {
729      if (!StaticUtils.isValidSchemaElement(n, 0, n.length(), err))
730      {
731        errors.add(ERR_CTRL_PANEL_OID_NOT_VALID.get(err));
732        setPrimaryInvalid(lOID);
733        err = new LocalizableMessageBuilder();
734      }
735      else
736      {
737        LocalizableMessage elementType = NewAttributePanel.getSchemaElementType(n, schema);
738        if (elementType != null)
739        {
740          errors.add(ERR_CTRL_PANEL_OID_ALREADY_IN_USE.get(n, elementType));
741          setPrimaryInvalid(lOID);
742        }
743      }
744    }
745
746    Collection<String> aliases = getAliases();
747    Collection<String> oldAliases = getAliases(objectClass);
748
749    if (!aliases.equals(oldAliases))
750    {
751      for (String alias : aliases)
752      {
753        if (alias.trim().length() == 0)
754        {
755          errors.add(ERR_CTRL_PANEL_EMPTY_ALIAS.get());
756          setPrimaryInvalid(lAliases);
757        }
758        else
759        {
760          boolean notPreviouslyDefined = !containsIgnoreCase(oldAliases, alias);
761          if (notPreviouslyDefined)
762          {
763            LocalizableMessage elementType =
764              NewAttributePanel.getSchemaElementType(alias, schema);
765            if (elementType != null)
766            {
767              errors.add(ERR_CTRL_PANEL_ALIAS_ALREADY_IN_USE.get(n, elementType));
768              setPrimaryInvalid(lAliases);
769            }
770          }
771        }
772      }
773    }
774
775   //validate the superiority.
776    for(ObjectClass superior : getObjectClassSuperiors())
777    {
778      validateSuperiority(superior, errors);
779    }
780    checkCompatibleSuperiors(getObjectClassSuperiors(), getObjectClassType(),
781        errors);
782
783    if (errors.isEmpty())
784    {
785      ProgressDialog dlg = new ProgressDialog(
786          Utilities.createFrame(),
787          Utilities.getParentDialog(this),
788          INFO_CTRL_PANEL_MODIFY_ATTRIBUTE_TITLE.get(), getInfo());
789
790      ModifyObjectClassTask newTask = new ModifyObjectClassTask(getInfo(),
791          dlg, objectClass, getNewObjectClass());
792      for (ConfigurationElementCreatedListener listener :
793        getConfigurationElementCreatedListeners())
794      {
795        newTask.addConfigurationElementCreatedListener(listener);
796      }
797      for (Task task : getInfo().getTasks())
798      {
799        task.canLaunch(newTask, errors);
800      }
801      if (errors.isEmpty())
802      {
803        launchOperation(newTask,
804            INFO_CTRL_PANEL_MODIFYING_OBJECTCLASS_SUMMARY.get(ocName),
805            INFO_CTRL_PANEL_MODIFYING_OBJECTCLASS_COMPLETE.get(),
806            INFO_CTRL_PANEL_MODIFYING_OBJECTCLASS_SUCCESSFUL.get(ocName),
807            ERR_CTRL_PANEL_MODIFYING_OBJECTCLASS_ERROR_SUMMARY.get(),
808            ERR_CTRL_PANEL_MODIFYING_OBJECTCLASS_ERROR_DETAILS.get(ocName),
809            null,
810            dlg);
811        dlg.setVisible(true);
812      }
813    }
814
815    if (!errors.isEmpty())
816    {
817      displayErrorDialog(errors);
818    }
819  }
820
821  private boolean containsIgnoreCase(Collection<String> col, String toFind)
822  {
823    for (String s : col)
824    {
825      if (s.equalsIgnoreCase(toFind))
826      {
827        return true;
828      }
829    }
830    return false;
831  }
832
833  private void validateSuperiority(ObjectClass superior, List<LocalizableMessage> errors)
834  {
835    if(superior.getNameOrOID().equalsIgnoreCase(objectClass.getNameOrOID()))
836    {
837      errors.add(ERR_CTRL_PANEL_OBJECTCLASS_CANNOT_BE_ITS_SUPERIOR.get());
838      setPrimaryInvalid(lSuperior);
839      return;
840    }
841    for (ObjectClass obj : superior.getSuperiorClasses())
842    {
843      if (superior.getNameOrOID().equalsIgnoreCase(obj.getNameOrOID()))
844      {
845         errors.add(
846                ERR_CTRL_PANEL_OBJECTCLASS_IS_SUPERIOR_OF_SUPERIOR.get(
847                obj.getNameOrOID()));
848            setPrimaryInvalid(lSuperior);
849        return;
850      }
851      validateSuperiority(obj,errors);
852    }
853  }
854
855  private void checkEnableSaveChanges()
856  {
857    if (!ignoreChangeEvents)
858    {
859      saveChanges.setEnabled(hasChanged());
860    }
861  }
862
863  private boolean hasChanged()
864  {
865    if (objectClass != null)
866    {
867      try
868      {
869        return !objectClass.toString().equals(getNewObjectClass().toString());
870      }
871      catch (Throwable t)
872      {
873        return true;
874      }
875    }
876    return false;
877  }
878
879  private Set<String> getAliases()
880  {
881    Set<String> al = new LinkedHashSet<>();
882    String s = aliases.getText().trim();
883    if (s.length() > 0)
884    {
885      String[] a = s.split(",");
886      for (String alias : a)
887      {
888        al.add(alias.trim());
889      }
890    }
891    return al;
892  }
893
894  private String getObjectClassName()
895  {
896    return name.getText().trim();
897  }
898
899  private String getOID()
900  {
901    String o = oid.getText().trim();
902    if (o.length() == 0)
903    {
904      o = getObjectClassName()+"-oid";
905    }
906    return o;
907  }
908
909  private Map<String, List<String>> getExtraProperties()
910  {
911    Map<String, List<String>> map = new HashMap<>();
912    String f = file.getText().trim();
913    if (f.length() > 0)
914    {
915      map.put(ServerConstants.SCHEMA_PROPERTY_FILENAME, newArrayList(f));
916    }
917    String or = origin.getText().trim();
918    if (or.length() > 0)
919    {
920      map.put(ServerConstants.SCHEMA_PROPERTY_ORIGIN, newArrayList(or));
921    }
922    return map;
923  }
924
925  private ArrayList<String> getAllNames()
926  {
927    ArrayList<String> al = new ArrayList<>();
928    al.add(getObjectClassName());
929    al.addAll(getAliases());
930    return al;
931  }
932
933  private String getDescription()
934  {
935    return description.getText().trim();
936  }
937
938  private Set<ObjectClass> getObjectClassSuperiors()
939  {
940    return superiors.getSelectedSuperiors();
941  }
942
943  private ObjectClassType getObjectClassType()
944  {
945    return (ObjectClassType)type.getSelectedItem();
946  }
947
948  private Set<AttributeType> getRequiredAttributes()
949  {
950    return intersect(attributes.getSelectedListModel1().getData(), inheritedRequiredAttributes);
951  }
952
953  private Set<AttributeType> getOptionalAttributes()
954  {
955    return intersect(attributes.getSelectedListModel2().getData(), inheritedOptionalAttributes);
956  }
957
958  private Set<AttributeType> intersect(Set<AttributeType> set1, Set<AttributeType> set2)
959  {
960    HashSet<AttributeType> attrs = new HashSet<>(set1);
961    attrs.removeAll(set2);
962    return attrs;
963  }
964
965  private ObjectClass getNewObjectClass()
966  {
967    return new SchemaBuilder(schema.getSchemaNG()).buildObjectClass(getOID())
968        .names(getAllNames())
969        .description(getDescription())
970        .superiorObjectClasses(getNameOrOIDsForOCs(getObjectClassSuperiors()))
971        .requiredAttributes(getNameOrOIDsForATs(getRequiredAttributes()))
972        .optionalAttributes(getNameOrOIDsForATs(getOptionalAttributes()))
973        .type(getObjectClassType())
974        .obsolete(obsolete.isSelected())
975        .extraProperties(getExtraProperties())
976        .addToSchema()
977        .toSchema()
978        .getObjectClass(getOID());
979  }
980
981  private void updateAttributes()
982  {
983    int[][] selected =
984    {
985      attributes.getAvailableList().getSelectedIndices(),
986      attributes.getSelectedList1().getSelectedIndices(),
987      attributes.getSelectedList2().getSelectedIndices()
988    };
989    JList[] lists =
990    {
991        attributes.getAvailableList(),
992        attributes.getSelectedList1(),
993        attributes.getSelectedList2()
994    };
995    attributes.getAvailableListModel().clear();
996    Collection<AttributeType> allAttrs = schema.getAttributeTypes();
997    attributes.getAvailableListModel().addAll(allAttrs);
998
999    HashSet<AttributeType> toDelete = new HashSet<>();
1000    for (AttributeType attr : attributes.getSelectedListModel1().getData())
1001    {
1002      if (!allAttrs.contains(attr))
1003      {
1004        toDelete.add(attr);
1005      }
1006      else
1007      {
1008        attributes.getAvailableListModel().remove(attr);
1009      }
1010    }
1011    for (AttributeType attr : toDelete)
1012    {
1013      attributes.getSelectedListModel1().remove(attr);
1014    }
1015
1016    toDelete = new HashSet<>();
1017    for (AttributeType attr : attributes.getSelectedListModel2().getData())
1018    {
1019      if (!allAttrs.contains(attr))
1020      {
1021        toDelete.add(attr);
1022      }
1023      else
1024      {
1025        attributes.getAvailableListModel().remove(attr);
1026      }
1027    }
1028    for (AttributeType attr : toDelete)
1029    {
1030      attributes.getSelectedListModel1().remove(attr);
1031    }
1032
1033    int i = 0;
1034    for (int[] sel : selected)
1035    {
1036      if (sel != null)
1037      {
1038        ArrayList<Integer> indexes = new ArrayList<>();
1039        for (int element : sel)
1040        {
1041          if (element < lists[i].getModel().getSize())
1042          {
1043            indexes.add(element);
1044          }
1045        }
1046        int[] newSelection = new int[indexes.size()];
1047        for (int j=0; j<newSelection.length; j++)
1048        {
1049          newSelection[j] = indexes.get(j);
1050        }
1051        lists[i].setSelectedIndices(newSelection);
1052      }
1053      i++;
1054    }
1055  }
1056
1057  private void updateAttributesWithParent(boolean notify)
1058  {
1059 // Remove the previous inherited attributes.
1060    for (AttributeType attr : inheritedRequiredAttributes)
1061    {
1062      attributes.getAvailableListModel().add(attr);
1063      attributes.getSelectedListModel1().remove(attr);
1064    }
1065    for (AttributeType attr : inheritedOptionalAttributes)
1066    {
1067      attributes.getAvailableListModel().add(attr);
1068      attributes.getSelectedListModel2().remove(attr);
1069    }
1070
1071    inheritedOptionalAttributes.clear();
1072    inheritedRequiredAttributes.clear();
1073    for (ObjectClass p : getObjectClassSuperiors())
1074    {
1075      inheritedRequiredAttributes.addAll(p.getRequiredAttributes());
1076      inheritedOptionalAttributes.addAll(p.getOptionalAttributes());
1077    }
1078    for (AttributeType attr : inheritedRequiredAttributes)
1079    {
1080      attributes.getAvailableListModel().remove(attr);
1081      attributes.getSelectedListModel1().add(attr);
1082    }
1083    for (AttributeType attr : inheritedOptionalAttributes)
1084    {
1085      attributes.getAvailableListModel().remove(attr);
1086      attributes.getSelectedListModel2().add(attr);
1087    }
1088
1089    Collection<AttributeType> unmovableItems = new ArrayList<>(inheritedRequiredAttributes);
1090    unmovableItems.addAll(inheritedOptionalAttributes);
1091    attributes.setUnmovableItems(unmovableItems);
1092
1093    if (notify)
1094    {
1095      notifyAttributesChanged();
1096    }
1097  }
1098
1099  private void notifyAttributesChanged()
1100  {
1101    attributes.getAvailableListModel().fireContentsChanged(
1102        attributes.getAvailableList(), 0,
1103        attributes.getAvailableListModel().getSize() - 1);
1104    attributes.getSelectedListModel1().fireContentsChanged(
1105        attributes.getSelectedList1(), 0,
1106        attributes.getSelectedListModel1().getSize() - 1);
1107    attributes.getSelectedListModel2().fireContentsChanged(
1108        attributes.getSelectedList2(), 0,
1109        attributes.getSelectedListModel2().getSize() - 1);
1110  }
1111
1112  /**
1113   * A renderer for the attribute lists.  The renderer basically marks the
1114   * inherited attributes with an asterisk.
1115   */
1116  private class AttributeTypeCellRenderer implements ListCellRenderer
1117  {
1118    private ListCellRenderer defaultRenderer;
1119
1120    /** Renderer constructor. */
1121    public AttributeTypeCellRenderer()
1122    {
1123      defaultRenderer = attributes.getAvailableList().getCellRenderer();
1124    }
1125
1126    @Override
1127    public Component getListCellRendererComponent(JList list, Object value,
1128        int index, boolean isSelected, boolean cellHasFocus)
1129    {
1130      if (value instanceof AttributeType)
1131      {
1132        AttributeType attr = (AttributeType)value;
1133        if (inheritedOptionalAttributes.contains(value) ||
1134            inheritedRequiredAttributes.contains(value))
1135        {
1136          value = attr.getNameOrOID()+ " (*)";
1137        }
1138        else
1139        {
1140          value = attr.getNameOrOID();
1141        }
1142      }
1143      return defaultRenderer.getListCellRendererComponent(list, value, index,
1144          isSelected, cellHasFocus);
1145    }
1146  }
1147}