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