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-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027 028package org.opends.guitools.controlpanel.ui; 029 030import static org.opends.messages.AdminToolMessages.*; 031import static org.opends.server.util.CollectionUtils.*; 032 033import java.awt.Container; 034import java.awt.GridBagConstraints; 035import java.text.ParseException; 036import java.util.ArrayList; 037import java.util.Iterator; 038import java.util.LinkedHashSet; 039import java.util.List; 040import java.util.Set; 041import java.util.SortedSet; 042import java.util.TreeSet; 043 044import javax.swing.JLabel; 045import javax.swing.tree.TreePath; 046 047import org.opends.guitools.controlpanel.datamodel.BinaryValue; 048import org.opends.guitools.controlpanel.datamodel.CustomSearchResult; 049import org.opends.guitools.controlpanel.datamodel.ObjectClassValue; 050import org.opends.guitools.controlpanel.event.ConfigurationChangeEvent; 051import org.opends.guitools.controlpanel.event.LDAPEntryChangedEvent; 052import org.opends.guitools.controlpanel.event.LDAPEntryChangedListener; 053import org.opends.guitools.controlpanel.ui.nodes.BasicNode; 054import org.opends.guitools.controlpanel.util.Utilities; 055import org.forgerock.i18n.LocalizableMessage; 056import org.forgerock.opendj.ldap.schema.Syntax; 057import org.opends.server.schema.SchemaConstants; 058import org.opends.server.types.AttributeType; 059import org.forgerock.opendj.ldap.ByteString; 060import org.opends.server.types.Attributes; 061import org.opends.server.types.Entry; 062import org.opends.server.types.ObjectClass; 063import org.forgerock.opendj.ldap.schema.ObjectClassType; 064import org.opends.server.types.OpenDsException; 065import org.opends.server.types.RDN; 066import org.opends.server.types.Schema; 067import org.opends.server.util.Base64; 068import org.opends.server.util.ServerConstants; 069 070/** 071 * Abstract class containing code shared by the different LDAP entry view 072 * panels (Simplified View, Attribute View and LDIF View). 073 * 074 */ 075public abstract class ViewEntryPanel extends StatusGenericPanel 076{ 077 private static final long serialVersionUID = -1908757626234678L; 078 /** 079 * The read-only attributes as they appear on the schema. 080 */ 081 protected SortedSet<String> schemaReadOnlyAttributes = new TreeSet<>(); 082 /** 083 * The read-only attributes in lower case. 084 */ 085 protected SortedSet<String> schemaReadOnlyAttributesLowerCase = new TreeSet<>(); 086 /** 087 * The editable operational attributes. 088 */ 089 protected SortedSet<String> editableOperationalAttrNames = new TreeSet<>(); 090 private JLabel title= Utilities.createDefaultLabel(); 091 092 private Set<LDAPEntryChangedListener> listeners = new LinkedHashSet<>(); 093 094 /** 095 * Whether the entry change events should be ignored or not. 096 */ 097 protected boolean ignoreEntryChangeEvents; 098 099 /** 100 * Static boolean used to know whether only attributes with values should be 101 * displayed or not. 102 */ 103 protected static boolean displayOnlyWithAttrs = true; 104 105 /** {@inheritDoc} */ 106 public void okClicked() 107 { 108 // No ok button 109 } 110 111 /** 112 * Returns an Entry object representing what the panel is displaying. 113 * @return an Entry object representing what the panel is displaying. 114 * @throws OpenDsException if the entry cannot be generated (in particular if 115 * the user provided invalid data). 116 */ 117 public abstract Entry getEntry() throws OpenDsException; 118 119 /** 120 * Updates the contents of the panel. 121 * @param sr the search result to be used to update the panel. 122 * @param isReadOnly whether the entry is read-only or not. 123 * @param path the tree path associated with the entry in the tree. 124 */ 125 public abstract void update(CustomSearchResult sr, boolean isReadOnly, 126 TreePath path); 127 128 /** 129 * Adds a title panel to the container. 130 * @param c the container where the title panel must be added. 131 * @param gbc the grid bag constraints to be used. 132 */ 133 protected void addTitlePanel(Container c, GridBagConstraints gbc) 134 { 135 c.add(title, gbc); 136 } 137 138 /** 139 * Whether the schema must be checked or not. 140 * @return <CODE>true</CODE> if the server is configured to check schema and 141 * <CODE>false</CODE> otherwise. 142 */ 143 protected boolean checkSchema() 144 { 145 return getInfo().getServerDescriptor().isSchemaEnabled(); 146 } 147 148 /** 149 * Adds an LDAP entry change listener. 150 * @param listener the listener. 151 */ 152 public void addLDAPEntryChangedListener(LDAPEntryChangedListener listener) 153 { 154 listeners.add(listener); 155 } 156 157 /** 158 * Removes an LDAP entry change listener. 159 * @param listener the listener. 160 */ 161 public void removeLDAPEntryChangedListener(LDAPEntryChangedListener listener) 162 { 163 listeners.remove(listener); 164 } 165 166 /** {@inheritDoc} */ 167 public boolean requiresBorder() 168 { 169 return true; 170 } 171 172 /** 173 * Returns the DN of the entry that the user is editing (it might differ 174 * from the DN of the entry in the tree if the user modified the DN). 175 * @return the DN of the entry that the user is editing. 176 */ 177 protected abstract String getDisplayedDN(); 178 179 /** Notifies the entry changed listeners that the entry changed. */ 180 protected void notifyListeners() 181 { 182 if (ignoreEntryChangeEvents) 183 { 184 return; 185 } 186 // TODO: With big entries this is pretty slow. Until there is a fix, try 187 // simply to update the dn 188 Entry entry = null; 189 String dn = getDisplayedDN(); 190 if (dn != null && !dn.equals(title.getText())) 191 { 192 title.setText(dn); 193 } 194 LDAPEntryChangedEvent ev = new LDAPEntryChangedEvent(this, entry); 195 for (LDAPEntryChangedListener listener : listeners) 196 { 197 listener.entryChanged(ev); 198 } 199 } 200 201 /** 202 * Updates the title panel with the provided entry. 203 * @param sr the search result. 204 * @param path the path to the node of the entry selected in the tree. Used 205 * to display the same icon as in the tree. 206 */ 207 protected void updateTitle(CustomSearchResult sr, TreePath path) 208 { 209 String dn = sr.getDN(); 210 if (dn != null && dn.length() > 0) 211 { 212 title.setText(sr.getDN()); 213 } 214 else if (path != null) 215 { 216 BasicNode node = (BasicNode)path.getLastPathComponent(); 217 title.setText(node.getDisplayName()); 218 } 219 220 if (path != null) 221 { 222 BasicNode node = (BasicNode)path.getLastPathComponent(); 223 title.setIcon(node.getIcon()); 224 } 225 else 226 { 227 title.setIcon(null); 228 } 229 230 List<Object> ocs = 231 sr.getAttributeValues(ServerConstants.OBJECTCLASS_ATTRIBUTE_TYPE_NAME); 232 Schema schema = getInfo().getServerDescriptor().getSchema(); 233 if (!ocs.isEmpty() && schema != null) 234 { 235 ObjectClassValue ocDesc = getObjectClassDescriptor(ocs, schema); 236 StringBuilder sb = new StringBuilder(); 237 sb.append("<html>"); 238 if (ocDesc.getStructural() != null) 239 { 240 sb.append(INFO_CTRL_OBJECTCLASS_DESCRIPTOR.get(ocDesc.getStructural())); 241 } 242 if (!ocDesc.getAuxiliary().isEmpty()) 243 { 244 if (sb.length() > 0) 245 { 246 sb.append("<br>"); 247 } 248 sb.append(INFO_CTRL_AUXILIARY_OBJECTCLASS_DESCRIPTOR.get( 249 Utilities.getStringFromCollection(ocDesc.getAuxiliary(), ", "))); 250 } 251 title.setToolTipText(sb.toString()); 252 } 253 else 254 { 255 title.setToolTipText(null); 256 } 257 } 258 259 /** 260 * Returns an object class value representing all the object class values of 261 * the entry. 262 * @param ocValues the list of object class values. 263 * @param schema the schema. 264 * @return an object class value representing all the object class values of 265 * the entry. 266 */ 267 protected ObjectClassValue getObjectClassDescriptor(List<Object> ocValues, 268 Schema schema) 269 { 270 ObjectClass structuralObjectClass = null; 271 SortedSet<String> auxiliaryClasses = new TreeSet<>(); 272 for (Object o : ocValues) 273 { 274 ObjectClass objectClass = 275 schema.getObjectClass(((String)o).toLowerCase()); 276 if (objectClass != null) 277 { 278 if (objectClass.getObjectClassType() == ObjectClassType.STRUCTURAL) 279 { 280 if (structuralObjectClass == null) 281 { 282 structuralObjectClass = objectClass; 283 } 284 else 285 { 286 if (objectClass.isDescendantOf(structuralObjectClass)) 287 { 288 structuralObjectClass = objectClass; 289 } 290 } 291 } 292 else 293 { 294 String name = objectClass.getNameOrOID(); 295 if (!name.equals(SchemaConstants.TOP_OBJECTCLASS_NAME)) 296 { 297 auxiliaryClasses.add(objectClass.getNameOrOID()); 298 } 299 } 300 } 301 } 302 String structural = structuralObjectClass != null ? 303 structuralObjectClass.getNameOrOID() : null; 304 return new ObjectClassValue(structural, auxiliaryClasses); 305 } 306 307 /** 308 * Adds the values in the RDN to the entry definition. 309 * @param entry the entry to be updated. 310 */ 311 protected void addValuesInRDN(Entry entry) 312 { 313// Add the values in the RDN if they are not there 314 RDN rdn = entry.getName().rdn(); 315 for (int i=0; i<rdn.getNumValues(); i++) 316 { 317 String attrName = rdn.getAttributeName(i); 318 ByteString value = rdn.getAttributeValue(i); 319 List<org.opends.server.types.Attribute> attrs = 320 entry.getAttribute(attrName.toLowerCase()); 321 boolean done = false; 322 if (attrs != null) 323 { 324 for (org.opends.server.types.Attribute attr : attrs) 325 { 326 if (attr.getNameWithOptions().equals(attrName)) 327 { 328 ArrayList<ByteString> newValues = new ArrayList<>(); 329 Iterator<ByteString> it = attr.iterator(); 330 while (it.hasNext()) 331 { 332 newValues.add(it.next()); 333 } 334 newValues.add(value); 335 entry.addAttribute(attr, newValues); 336 done = true; 337 break; 338 } 339 } 340 } 341 if (!done) 342 { 343 org.opends.server.types.Attribute attr = 344 Attributes.create(rdn.getAttributeType(i), value); 345 entry.addAttribute(attr, newArrayList(value)); 346 } 347 } 348 } 349 350 /** {@inheritDoc} */ 351 public LocalizableMessage getTitle() 352 { 353 return INFO_CTRL_PANEL_EDIT_LDAP_ENTRY_TITLE.get(); 354 } 355 356 /** {@inheritDoc} */ 357 public void configurationChanged(ConfigurationChangeEvent ev) 358 { 359 Schema schema = ev.getNewDescriptor().getSchema(); 360 if (schema != null && schemaReadOnlyAttributes.isEmpty()) 361 { 362 schemaReadOnlyAttributes.clear(); 363 schemaReadOnlyAttributesLowerCase.clear(); 364 for (AttributeType attr : schema.getAttributeTypes().values()) 365 { 366 if (attr.isNoUserModification()) 367 { 368 String attrName = attr.getNameOrOID(); 369 schemaReadOnlyAttributes.add(attrName); 370 schemaReadOnlyAttributesLowerCase.add(attrName.toLowerCase()); 371 } 372 else if (attr.isOperational()) 373 { 374 editableOperationalAttrNames.add(attr.getNameOrOID()); 375 } 376 } 377 } 378 } 379 380 /** 381 * Appends the LDIF lines corresponding to the different values of an 382 * attribute to the provided StringBuilder. 383 * @param sb the StringBuilder that must be updated. 384 * @param attrName the attribute name. 385 * @param values the attribute values. 386 */ 387 protected void appendLDIFLines(StringBuilder sb, String attrName, 388 List<Object> values) 389 { 390 for (Object value : values) 391 { 392 appendLDIFLine(sb, attrName, value); 393 } 394 } 395 396 /** 397 * Appends the LDIF line corresponding to the value of an 398 * attribute to the provided StringBuilder. 399 * @param sb the StringBuilder that must be updated. 400 * @param attrName the attribute name. 401 * @param value the attribute value. 402 */ 403 protected void appendLDIFLine(StringBuilder sb, String attrName, Object value) 404 { 405 if (value instanceof ObjectClassValue) 406 { 407 ObjectClassValue ocValue = (ObjectClassValue)value; 408 if (ocValue.getStructural() != null) 409 { 410 sb.append("\n"); 411 sb.append(attrName).append(": ").append(ocValue.getStructural()); 412 Schema schema = getInfo().getServerDescriptor().getSchema(); 413 if (schema != null) 414 { 415 ObjectClass oc = 416 schema.getObjectClass(ocValue.getStructural().toLowerCase()); 417 if (oc != null) 418 { 419 Set<String> names = getObjectClassSuperiorValues(oc); 420 for (String name : names) 421 { 422 sb.append("\n"); 423 sb.append(attrName).append(": ").append(name); 424 } 425 } 426 } 427 } 428 for (String v : ocValue.getAuxiliary()) 429 { 430 sb.append("\n"); 431 sb.append(attrName).append(": ").append(v); 432 } 433 } 434 else if (value instanceof byte[]) 435 { 436 if (((byte[])value).length > 0) 437 { 438 sb.append("\n"); 439 sb.append(attrName).append(":: ").append(Base64.encode((byte[])value)); 440 } 441 } 442 else if (value instanceof BinaryValue) 443 { 444 sb.append("\n"); 445 sb.append(attrName).append(":: ").append(((BinaryValue)value).getBase64()); 446 } 447 else if (String.valueOf(value).trim().length() > 0) 448 { 449 sb.append("\n"); 450 sb.append(attrName).append(": ").append(value); 451 } 452 } 453 454 /** 455 * Returns <CODE>true</CODE> if the provided attribute name has binary syntax 456 * and <CODE>false</CODE> otherwise. 457 * @param attrName the attribute name. 458 * @return <CODE>true</CODE> if the provided attribute name has binary syntax 459 * and <CODE>false</CODE> otherwise. 460 */ 461 protected boolean isBinary(String attrName) 462 { 463 Schema schema = getInfo().getServerDescriptor().getSchema(); 464 return Utilities.hasBinarySyntax(attrName, schema); 465 } 466 467 /** 468 * Returns <CODE>true</CODE> if the provided attribute name has password 469 * syntax and <CODE>false</CODE> otherwise. 470 * @param attrName the attribute name. 471 * @return <CODE>true</CODE> if the provided attribute name has password 472 * syntax and <CODE>false</CODE> otherwise. 473 */ 474 protected boolean isPassword(String attrName) 475 { 476 Schema schema = getInfo().getServerDescriptor().getSchema(); 477 return Utilities.hasPasswordSyntax(attrName, schema); 478 } 479 480 /** 481 * Returns <CODE>true</CODE> if the provided attribute name has certificate 482 * syntax and <CODE>false</CODE> otherwise. 483 * @param attrName the attribute name. 484 * @param schema the schema. 485 * @return <CODE>true</CODE> if the provided attribute name has certificate 486 * syntax and <CODE>false</CODE> otherwise. 487 */ 488 protected boolean hasCertificateSyntax(String attrName, Schema schema) 489 { 490 boolean isCertificate = false; 491 // Check all the attributes that we consider binaries. 492 if (schema != null) 493 { 494 AttributeType attr = schema.getAttributeType( 495 Utilities.getAttributeNameWithoutOptions(attrName).toLowerCase()); 496 if (attr != null) 497 { 498 Syntax syntax = attr.getSyntax(); 499 if (syntax != null) 500 { 501 isCertificate = syntax.getOID().equals( 502 SchemaConstants.SYNTAX_CERTIFICATE_OID); 503 } 504 } 505 } 506 return isCertificate; 507 } 508 509 /** 510 * Gets the values associated with a given attribute. The values are the 511 * ones displayed in the panel. 512 * @param attrName the attribute name. 513 * @return the values associated with a given attribute. 514 */ 515 protected abstract List<Object> getValues(String attrName); 516 517 /** 518 * Sets the values displayed in the panel for a given attribute in the 519 * provided search result. 520 * @param sr the search result to be updated. 521 * @param attrName the attribute name. 522 */ 523 protected void setValues(CustomSearchResult sr, String attrName) 524 { 525 List<Object> values = getValues(attrName); 526 List<Object> valuesToSet = new ArrayList<>(); 527 for (Object value : values) 528 { 529 if (value instanceof ObjectClassValue) 530 { 531 ObjectClassValue ocValue = (ObjectClassValue)value; 532 if (ocValue.getStructural() != null) 533 { 534 valuesToSet.add(ocValue.getStructural()); 535 } 536 valuesToSet.addAll(ocValue.getAuxiliary()); 537 } 538 else if (value instanceof byte[]) 539 { 540 valuesToSet.add(value); 541 } 542 else if (value instanceof BinaryValue) 543 { 544 try 545 { 546 valuesToSet.add(((BinaryValue)value).getBytes()); 547 } 548 catch (ParseException pe) 549 { 550 throw new RuntimeException("Unexpected error: "+pe, pe); 551 } 552 } 553 else 554 { 555 if (String.valueOf(value).trim().length() > 0) 556 { 557 valuesToSet.add(String.valueOf(value)); 558 } 559 } 560 } 561 if (!valuesToSet.isEmpty()) 562 { 563 sr.set(attrName, valuesToSet); 564 } 565 } 566 567 /** 568 * Returns <CODE>true</CODE> if the provided attribute name is an editable 569 * attribute and <CODE>false</CODE> otherwise. 570 * @param attrName the attribute name. 571 * @param schema the schema. 572 * @return <CODE>true</CODE> if the provided attribute name is an editable 573 * attribute and <CODE>false</CODE> otherwise. 574 */ 575 public static boolean isEditable(String attrName, Schema schema) 576 { 577 boolean isEditable = false; 578 attrName = Utilities.getAttributeNameWithoutOptions(attrName); 579 if (schema != null) 580 { 581 AttributeType attrType = schema.getAttributeType(attrName.toLowerCase()); 582 if (attrType != null) 583 { 584 isEditable = !attrType.isNoUserModification(); 585 } 586 } 587 return isEditable; 588 } 589 590 /** 591 * Returns the list of superior object classes (to top) for a given object 592 * class. 593 * @param oc the object class. 594 * @return the set of superior object classes for a given object classes. 595 */ 596 protected Set<String> getObjectClassSuperiorValues( 597 ObjectClass oc) 598 { 599 Set<String> names = new LinkedHashSet<>(); 600 Set<ObjectClass> parents = oc.getSuperiorClasses(); 601 if (parents != null && !parents.isEmpty()) 602 { 603 for (ObjectClass parent : parents) 604 { 605 names.add(parent.getNameOrOID()); 606 names.addAll(getObjectClassSuperiorValues(parent)); 607 } 608 } 609 return names; 610 } 611}