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.AttributeDescription; 039import org.forgerock.opendj.ldap.ByteString; 040import org.forgerock.opendj.ldap.schema.AttributeType; 041import org.forgerock.opendj.ldap.schema.ObjectClass; 042import org.forgerock.opendj.ldap.schema.ObjectClassType; 043import org.opends.guitools.controlpanel.datamodel.BinaryValue; 044import org.opends.guitools.controlpanel.datamodel.CustomSearchResult; 045import org.opends.guitools.controlpanel.datamodel.ObjectClassValue; 046import org.opends.guitools.controlpanel.event.ConfigurationChangeEvent; 047import org.opends.guitools.controlpanel.event.LDAPEntryChangedEvent; 048import org.opends.guitools.controlpanel.event.LDAPEntryChangedListener; 049import org.opends.guitools.controlpanel.ui.nodes.BasicNode; 050import org.opends.guitools.controlpanel.util.Utilities; 051import org.opends.server.schema.SchemaConstants; 052import org.opends.server.types.Attributes; 053import org.opends.server.types.Entry; 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, TreePath path); 103 104 /** 105 * Adds a title panel to the container. 106 * @param c the container where the title panel must be added. 107 * @param gbc the grid bag constraints to be used. 108 */ 109 protected void addTitlePanel(Container c, GridBagConstraints gbc) 110 { 111 c.add(title, gbc); 112 } 113 114 /** 115 * Whether the schema must be checked or not. 116 * @return <CODE>true</CODE> if the server is configured to check schema and 117 * <CODE>false</CODE> otherwise. 118 */ 119 protected boolean checkSchema() 120 { 121 return getInfo().getServerDescriptor().isSchemaEnabled(); 122 } 123 124 /** 125 * Adds an LDAP entry change listener. 126 * @param listener the listener. 127 */ 128 public void addLDAPEntryChangedListener(LDAPEntryChangedListener listener) 129 { 130 listeners.add(listener); 131 } 132 133 /** 134 * Removes an LDAP entry change listener. 135 * @param listener the listener. 136 */ 137 public void removeLDAPEntryChangedListener(LDAPEntryChangedListener listener) 138 { 139 listeners.remove(listener); 140 } 141 142 @Override 143 public boolean requiresBorder() 144 { 145 return true; 146 } 147 148 /** 149 * Returns the DN of the entry that the user is editing (it might differ 150 * from the DN of the entry in the tree if the user modified the DN). 151 * @return the DN of the entry that the user is editing. 152 */ 153 protected abstract String getDisplayedDN(); 154 155 /** Notifies the entry changed listeners that the entry changed. */ 156 protected void notifyListeners() 157 { 158 if (ignoreEntryChangeEvents) 159 { 160 return; 161 } 162 // TODO: With big entries this is pretty slow. Until there is a fix, try 163 // simply to update the dn 164 String dn = getDisplayedDN(); 165 if (dn != null && !dn.equals(title.getText())) 166 { 167 title.setText(dn); 168 } 169 LDAPEntryChangedEvent ev = new LDAPEntryChangedEvent(this, null); 170 for (LDAPEntryChangedListener listener : listeners) 171 { 172 listener.entryChanged(ev); 173 } 174 } 175 176 /** 177 * Updates the title panel with the provided entry. 178 * @param sr the search result. 179 * @param path the path to the node of the entry selected in the tree. Used 180 * to display the same icon as in the tree. 181 */ 182 protected void updateTitle(CustomSearchResult sr, TreePath path) 183 { 184 String dn = sr.getDN(); 185 if (dn != null && dn.length() > 0) 186 { 187 title.setText(sr.getDN()); 188 } 189 else if (path != null) 190 { 191 BasicNode node = (BasicNode)path.getLastPathComponent(); 192 title.setText(node.getDisplayName()); 193 } 194 195 if (path != null) 196 { 197 BasicNode node = (BasicNode)path.getLastPathComponent(); 198 title.setIcon(node.getIcon()); 199 } 200 else 201 { 202 title.setIcon(null); 203 } 204 205 List<Object> ocs = 206 sr.getAttributeValues(ServerConstants.OBJECTCLASS_ATTRIBUTE_TYPE_NAME); 207 Schema schema = getInfo().getServerDescriptor().getSchema(); 208 if (!ocs.isEmpty() && schema != null) 209 { 210 ObjectClassValue ocDesc = getObjectClassDescriptor(ocs, schema); 211 StringBuilder sb = new StringBuilder(); 212 sb.append("<html>"); 213 if (ocDesc.getStructural() != null) 214 { 215 sb.append(INFO_CTRL_OBJECTCLASS_DESCRIPTOR.get(ocDesc.getStructural())); 216 } 217 if (!ocDesc.getAuxiliary().isEmpty()) 218 { 219 if (sb.length() > 0) 220 { 221 sb.append("<br>"); 222 } 223 sb.append(INFO_CTRL_AUXILIARY_OBJECTCLASS_DESCRIPTOR.get( 224 Utilities.getStringFromCollection(ocDesc.getAuxiliary(), ", "))); 225 } 226 title.setToolTipText(sb.toString()); 227 } 228 else 229 { 230 title.setToolTipText(null); 231 } 232 } 233 234 /** 235 * Returns an object class value representing all the object class values of 236 * the entry. 237 * @param ocValues the list of object class values. 238 * @param schema the schema. 239 * @return an object class value representing all the object class values of 240 * the entry. 241 */ 242 protected ObjectClassValue getObjectClassDescriptor(List<Object> ocValues, 243 Schema schema) 244 { 245 ObjectClass structuralObjectClass = null; 246 SortedSet<String> auxiliaryClasses = new TreeSet<>(); 247 for (Object o : ocValues) 248 { 249 ObjectClass objectClass = schema.getObjectClass(((String) o)); 250 if (!objectClass.isPlaceHolder()) 251 { 252 if (objectClass.getObjectClassType() == ObjectClassType.STRUCTURAL) 253 { 254 if (structuralObjectClass == null || objectClass.isDescendantOf(structuralObjectClass)) 255 { 256 structuralObjectClass = objectClass; 257 } 258 } 259 else 260 { 261 String name = objectClass.getNameOrOID(); 262 if (!SchemaConstants.TOP_OBJECTCLASS_NAME.equals(name)) 263 { 264 auxiliaryClasses.add(objectClass.getNameOrOID()); 265 } 266 } 267 } 268 } 269 String structural = structuralObjectClass != null ? structuralObjectClass.getNameOrOID() : null; 270 return new ObjectClassValue(structural, auxiliaryClasses); 271 } 272 273 /** 274 * Adds the values in the RDN to the entry definition. 275 * @param entry the entry to be updated. 276 */ 277 protected void addValuesInRDN(Entry entry) 278 { 279 // Add the values in the RDN if they are not there 280 for (AVA ava : entry.getName().rdn()) 281 { 282 String attrName = ava.getAttributeName(); 283 ByteString value = ava.getAttributeValue(); 284 boolean done = false; 285 for (org.opends.server.types.Attribute attr : entry.getAttribute(attrName)) 286 { 287 // TODO JNR use Entry.getAttribute(AttributeDescription) instead? 288 if (attr.getAttributeDescription().toString().equals(attrName)) 289 { 290 List<ByteString> newValues = getValues(attr); 291 newValues.add(value); 292 entry.addAttribute(attr, newValues); 293 done = true; 294 break; 295 } 296 } 297 if (!done) 298 { 299 entry.addAttribute(Attributes.create(ava.getAttributeType(), value), newArrayList(value)); 300 } 301 } 302 } 303 304 private List<ByteString> getValues(org.opends.server.types.Attribute attr) 305 { 306 List<ByteString> newValues = new ArrayList<>(); 307 Iterator<ByteString> it = attr.iterator(); 308 while (it.hasNext()) 309 { 310 newValues.add(it.next()); 311 } 312 return newValues; 313 } 314 315 @Override 316 public LocalizableMessage getTitle() 317 { 318 return INFO_CTRL_PANEL_EDIT_LDAP_ENTRY_TITLE.get(); 319 } 320 321 @Override 322 public void configurationChanged(ConfigurationChangeEvent ev) 323 { 324 Schema schema = ev.getNewDescriptor().getSchema(); 325 if (schema != null && schemaReadOnlyAttributes.isEmpty()) 326 { 327 schemaReadOnlyAttributes.clear(); 328 schemaReadOnlyAttributesLowerCase.clear(); 329 for (AttributeType attr : schema.getAttributeTypes()) 330 { 331 if (attr.isNoUserModification()) 332 { 333 String attrName = attr.getNameOrOID(); 334 schemaReadOnlyAttributes.add(attrName); 335 schemaReadOnlyAttributesLowerCase.add(attrName.toLowerCase()); 336 } 337 else if (attr.isOperational()) 338 { 339 editableOperationalAttrNames.add(attr.getNameOrOID()); 340 } 341 } 342 } 343 } 344 345 /** 346 * Appends the LDIF line corresponding to the value of an 347 * attribute to the provided StringBuilder. 348 * @param sb the StringBuilder that must be updated. 349 * @param attrName the attribute name. 350 * @param value the attribute value. 351 */ 352 protected void appendLDIFLine(StringBuilder sb, String attrName, Object value) 353 { 354 if (value instanceof ObjectClassValue) 355 { 356 ObjectClassValue ocValue = (ObjectClassValue)value; 357 if (ocValue.getStructural() != null) 358 { 359 sb.append("\n"); 360 sb.append(attrName).append(": ").append(ocValue.getStructural()); 361 Schema schema = getInfo().getServerDescriptor().getSchema(); 362 if (schema != null) 363 { 364 ObjectClass oc = schema.getObjectClass(ocValue.getStructural()); 365 if (!oc.isPlaceHolder()) 366 { 367 Set<String> names = getObjectClassSuperiorValues(oc); 368 for (String name : names) 369 { 370 sb.append("\n"); 371 sb.append(attrName).append(": ").append(name); 372 } 373 } 374 } 375 } 376 for (String v : ocValue.getAuxiliary()) 377 { 378 sb.append("\n"); 379 sb.append(attrName).append(": ").append(v); 380 } 381 } 382 else if (value instanceof byte[]) 383 { 384 if (((byte[])value).length > 0) 385 { 386 sb.append("\n"); 387 sb.append(attrName).append(":: ").append(Base64.encode((byte[])value)); 388 } 389 } 390 else if (value instanceof BinaryValue) 391 { 392 sb.append("\n"); 393 sb.append(attrName).append(":: ").append(((BinaryValue)value).getBase64()); 394 } 395 else if (String.valueOf(value).trim().length() > 0) 396 { 397 sb.append("\n"); 398 sb.append(attrName).append(": ").append(value); 399 } 400 } 401 402 /** 403 * Returns <CODE>true</CODE> if the provided attribute name has binary syntax 404 * and <CODE>false</CODE> otherwise. 405 * @param attrName the attribute name. 406 * @return <CODE>true</CODE> if the provided attribute name has binary syntax 407 * and <CODE>false</CODE> otherwise. 408 */ 409 protected boolean isBinary(String attrName) 410 { 411 Schema schema = getInfo().getServerDescriptor().getSchema(); 412 return Utilities.hasBinarySyntax(attrName, schema); 413 } 414 415 /** 416 * Returns <CODE>true</CODE> if the provided attribute name has password 417 * syntax and <CODE>false</CODE> otherwise. 418 * @param attrName the attribute name. 419 * @return <CODE>true</CODE> if the provided attribute name has password 420 * syntax and <CODE>false</CODE> otherwise. 421 */ 422 protected boolean isPassword(String attrName) 423 { 424 Schema schema = getInfo().getServerDescriptor().getSchema(); 425 return Utilities.hasPasswordSyntax(attrName, schema); 426 } 427 428 /** 429 * Gets the values associated with a given attribute. The values are the 430 * ones displayed in the panel. 431 * @param attrName the attribute name. 432 * @return the values associated with a given attribute. 433 */ 434 protected abstract List<Object> getValues(String attrName); 435 436 /** 437 * Sets the values displayed in the panel for a given attribute in the 438 * provided search result. 439 * @param sr the search result to be updated. 440 * @param attrName the attribute name. 441 */ 442 protected void setValues(CustomSearchResult sr, String attrName) 443 { 444 List<Object> values = getValues(attrName); 445 List<Object> valuesToSet = new ArrayList<>(); 446 for (Object value : values) 447 { 448 if (value instanceof ObjectClassValue) 449 { 450 ObjectClassValue ocValue = (ObjectClassValue)value; 451 if (ocValue.getStructural() != null) 452 { 453 valuesToSet.add(ocValue.getStructural()); 454 } 455 valuesToSet.addAll(ocValue.getAuxiliary()); 456 } 457 else if (value instanceof byte[]) 458 { 459 valuesToSet.add(value); 460 } 461 else if (value instanceof BinaryValue) 462 { 463 try 464 { 465 valuesToSet.add(((BinaryValue)value).getBytes()); 466 } 467 catch (ParseException pe) 468 { 469 throw new RuntimeException("Unexpected error: "+pe, pe); 470 } 471 } 472 else if (String.valueOf(value).trim().length() > 0) 473 { 474 valuesToSet.add(String.valueOf(value)); 475 } 476 } 477 if (!valuesToSet.isEmpty()) 478 { 479 sr.set(attrName, valuesToSet); 480 } 481 } 482 483 /** 484 * Returns <CODE>true</CODE> if the provided attribute name is an editable 485 * attribute and <CODE>false</CODE> otherwise. 486 * @param attrName the attribute name. 487 * @param schema the schema. 488 * @return <CODE>true</CODE> if the provided attribute name is an editable 489 * attribute and <CODE>false</CODE> otherwise. 490 */ 491 public static boolean isEditable(String attrName, Schema schema) 492 { 493 attrName = AttributeDescription.valueOf(attrName).getNameOrOID(); 494 if (schema != null && schema.hasAttributeType(attrName)) 495 { 496 AttributeType attrType = schema.getAttributeType(attrName); 497 return !attrType.isNoUserModification(); 498 } 499 return false; 500 } 501 502 /** 503 * Returns the list of superior object classes (to top) for a given object class. 504 * @param oc the object class. 505 * @return the set of superior object classes for a given object classes. 506 */ 507 protected Set<String> getObjectClassSuperiorValues(ObjectClass oc) 508 { 509 Set<String> names = new LinkedHashSet<>(); 510 Set<ObjectClass> parents = oc.getSuperiorClasses(); 511 if (parents != null && !parents.isEmpty()) 512 { 513 for (ObjectClass parent : parents) 514 { 515 names.add(parent.getNameOrOID()); 516 names.addAll(getObjectClassSuperiorValues(parent)); 517 } 518 } 519 return names; 520 } 521}