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