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