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