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