opends/resource/config/config.ldif
@@ -458,6 +458,16 @@ ds-cfg-key-manager-provider-dn: cn=JKS,cn=Key Manager Providers,cn=config ds-cfg-trust-manager-provider-dn: cn=JKS,cn=Trust Manager Providers,cn=config dn: cn=LDIF Connection Handler,cn=Connection Handlers,cn=config objectClass: top objectClass: ds-cfg-connection-handler objectClass: ds-cfg-ldif-connection-handler cn: LDIF Connection Handler ds-cfg-connection-handler-class: org.opends.server.protocols.LDIFConnectionHandler ds-cfg-connection-handler-enabled: true ds-cfg-ldif-directory: config/auto-process-ldif ds-cfg-poll-interval: 5 seconds dn: cn=JMX Connection Handler,cn=Connection Handlers,cn=config objectClass: top objectClass: ds-cfg-connection-handler opends/resource/schema/02-config.ldif
@@ -1652,6 +1652,12 @@ attributeTypes: ( 1.3.6.1.4.1.26027.1.1.488 NAME 'ds-cfg-7-bit-clean-base-dn' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'OpenDS Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.26027.1.1.489 NAME 'ds-cfg-ldif-directory' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'OpenDS Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.26027.1.1.490 NAME 'ds-cfg-poll-interval' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'OpenDS Directory Server' ) objectClasses: ( 1.3.6.1.4.1.26027.1.2.1 NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled ) @@ -2457,4 +2463,8 @@ objectClasses: ( 1.3.6.1.4.1.26027.1.2.167 NAME 'ds-cfg-7-bit-clean-plugin' SUP ds-cfg-plugin STRUCTURAL MUST ds-cfg-7-bit-clean-attribute-type MAY ds-cfg-7-bit-clean-base-dn X-ORIGIN 'OpenDS Directory Server' ) objectClasses: ( 1.3.6.1.4.1.26027.1.2.168 NAME 'ds-cfg-ldif-connection-handler' SUP ds-cfg-connection-handler MUST ( ds-cfg-ldif-directory $ ds-cfg-poll-interval ) X-ORIGIN 'OpenDS Directory Server' ) opends/src/admin/defn/org/opends/server/admin/std/LDIFConnectionHandlerConfiguration.xml
New file @@ -0,0 +1,107 @@ <?xml version="1.0" encoding="utf-8"?> <!-- ! CDDL HEADER START ! ! The contents of this file are subject to the terms of the ! Common Development and Distribution License, Version 1.0 only ! (the "License"). You may not use this file except in compliance ! with the License. ! ! You can obtain a copy of the license at ! trunk/opends/resource/legal-notices/OpenDS.LICENSE ! or https://OpenDS.dev.java.net/OpenDS.LICENSE. ! See the License for the specific language governing permissions ! and limitations under the License. ! ! When distributing Covered Code, include this CDDL HEADER in each ! file and include the License file at ! trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, ! add the following below this CDDL HEADER, with the fields enclosed ! by brackets "[]" replaced with your own identifying information: ! Portions Copyright [yyyy] [name of copyright owner] ! ! CDDL HEADER END ! ! ! Portions Copyright 2007 Sun Microsystems, Inc. ! --> <adm:managed-object name="ldif-connection-handler" plural-name="ldif-connection-handlers" package="org.opends.server.admin.std" extends="connection-handler" xmlns:adm="http://www.opends.org/admin" xmlns:ldap="http://www.opends.org/admin-ldap"> <adm:synopsis> The <adm:user-friendly-name /> may be used to process changes in the server using internal operations, where the changes to process are read from an LDIF file. The connection handler will periodically look for the existence of a new file, will process the changes contained in that file as internal operations, and will write the result to an output file with comments indicating the result of the processing. </adm:synopsis> <adm:profile name="ldap"> <ldap:object-class> <ldap:oid>1.3.6.1.4.1.26027.1.2.168</ldap:oid> <ldap:name>ds-cfg-ldif-connection-handler</ldap:name> <ldap:superior>ds-cfg-connection-handler</ldap:superior> </ldap:object-class> </adm:profile> <adm:property-override name="java-implementation-class"> <adm:default-behavior> <adm:defined> <adm:value> org.opends.server.protocols.LDIFConnectionHandler </adm:value> </adm:defined> </adm:default-behavior> </adm:property-override> <adm:property name="ldif-directory" mandatory="true" multi-valued="false"> <adm:synopsis> Specifies the path to the directory in which the LDIF files should be placed. </adm:synopsis> <adm:default-behavior> <adm:defined> <adm:value>config/auto-process-ldif</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:string /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:oid>1.3.6.1.4.1.26027.1.1.489</ldap:oid> <ldap:name>ds-cfg-ldif-directory</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="poll-interval" mandatory="true" multi-valued="false"> <adm:synopsis> Specifies how frequently the LDIF connection handler should check the LDIF directory to determine whether a new LDIF file has been added. </adm:synopsis> <adm:default-behavior> <adm:defined> <adm:value>5 seconds</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:duration base-unit="ms" lower-limit="1" /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:oid>1.3.6.1.4.1.26027.1.1.490</ldap:oid> <ldap:name>ds-cfg-poll-interval</ldap:name> </ldap:attribute> </adm:profile> </adm:property> </adm:managed-object> opends/src/messages/messages/protocol.properties
@@ -1407,3 +1407,30 @@ supported over internal LDAP sockets MILD_ERR_INTERNALOS_STARTTLS_NOT_SUPPORTED_444=StartTLS operations are not \ supported over internal LDAP sockets SEVERE_WARN_LDIF_CONNHANDLER_LDIF_DIRECTORY_NOT_DIRECTORY_445=The value %s \ specified as the LDIF directory path for the LDIF connection handler defined \ in configuration entry %s exists but is not a directory. The specified path \ must be a directory. The LDIF connection handler will start, but will not \ be able to proces any changes until this path is changed to a directory MILD_WARN_LDIF_CONNHANDLER_LDIF_DIRECTORY_MISSING_446=The directory %s \ referenced by the LDIF connection handler defined in configuration entry %s \ does not exist. The LDIF connection handler will start, but will not be \ able to process any changes until this directory is created MILD_ERR_LDIF_CONNHANDLER_CANNOT_READ_CHANGE_RECORD_NONFATAL_447=An error \ occurred while trying to read a change record from the LDIF file: %s. This \ change will be skipped but processing on the LDIF file will continue MILD_ERR_LDIF_CONNHANDLER_CANNOT_READ_CHANGE_RECORD_FATAL_448=An error \ occurred while trying to read a change record from the LDIF file: %s. No \ further processing on this LDIF file can be performed INFO_LDIF_CONNHANDLER_UNKNOWN_CHANGETYPE_449=Unsupported change type %s INFO_LDIF_CONNHANDLER_RESULT_CODE_450=Result Code: %d (%s) INFO_LDIF_CONNHANDLER_ERROR_MESSAGE_451=Additional Info: %s INFO_LDIF_CONNHANDLER_MATCHED_DN_452=Matched DN: %s INFO_LDIF_CONNHANDLER_REFERRAL_URL_453=Referral URL: %s SEVERE_ERR_LDIF_CONNHANDLER_IO_ERROR_454=An I/O error occurred while the LDIF \ connection handler was processing LDIF file %s: %s SEVERE_ERR_LDIF_CONNHANDLER_CANNOT_RENAME_455=An error occurred while the \ LDIF connection handler was attempting to rename partially-processed file \ from %s to %s: %s SEVERE_ERR_LDIF_CONNHANDLER_CANNOT_DELETE_456=An error occurred while the \ LDIF connection handler was attempting to delete processed file %s: %s opends/src/server/org/opends/server/protocols/LDIFConnectionHandler.java
New file @@ -0,0 +1,693 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2007 Sun Microsystems, Inc. */ package org.opends.server.protocols; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import org.opends.messages.Message; import org.opends.messages.MessageBuilder; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.std.server.ConnectionHandlerCfg; import org.opends.server.admin.std.server.LDIFConnectionHandlerCfg; import org.opends.server.api.AlertGenerator; import org.opends.server.api.ClientConnection; import org.opends.server.api.ConnectionHandler; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.protocols.internal.InternalClientConnection; import org.opends.server.types.ConfigChangeResult; import org.opends.server.types.DebugLogLevel; import org.opends.server.types.DirectoryConfig; import org.opends.server.types.DN; import org.opends.server.types.ExistingFileBehavior; import org.opends.server.types.HostPort; import org.opends.server.types.LDIFExportConfig; import org.opends.server.types.LDIFImportConfig; import org.opends.server.types.Operation; import org.opends.server.types.ResultCode; import org.opends.server.util.AddChangeRecordEntry; import org.opends.server.util.ChangeRecordEntry; import org.opends.server.util.DeleteChangeRecordEntry; import org.opends.server.util.LDIFException; import org.opends.server.util.LDIFReader; import org.opends.server.util.LDIFWriter; import org.opends.server.util.ModifyChangeRecordEntry; import org.opends.server.util.ModifyDNChangeRecordEntry; import org.opends.server.util.TimeThread; import static org.opends.messages.ProtocolMessages.*; import static org.opends.server.loggers.ErrorLogger.*; import static org.opends.server.loggers.debug.DebugLogger.*; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.StaticUtils.*; /** * This class defines an LDIF connection handler, which can be used to watch for * new LDIF files to be placed in a specified directory. If a new LDIF file is * detected, the connection handler will process any changes contained in that * file as internal operations. */ public final class LDIFConnectionHandler extends ConnectionHandler<LDIFConnectionHandlerCfg> implements ConfigurationChangeListener<LDIFConnectionHandlerCfg>, AlertGenerator { /** * The debug log tracer for this class. */ private static final DebugTracer TRACER = getTracer(); // Indicates whether this connection handler is currently stopped. private volatile boolean isStopped; // Indicates whether we should stop this connection handler. private volatile boolean stopRequested; // The path to the directory to watch for new LDIF files. private File ldifDirectory; // The internal client connection that will be used for all processing. private InternalClientConnection conn; // The current configuration for this LDIF connection handler. private LDIFConnectionHandlerCfg currentConfig; // The thread used to run the connection handler. private Thread connectionHandlerThread; /** * Creates a new instance of this connection handler. All initialization * should be performed in the {@code initializeConnectionHandler} method. */ public LDIFConnectionHandler() { super("LDIFConnectionHandler"); isStopped = true; stopRequested = false; connectionHandlerThread = null; } /** * {@inheritDoc} */ @Override() public void initializeConnectionHandler(LDIFConnectionHandlerCfg configuration) { ldifDirectory = new File(configuration.getLDIFDirectory()); if (ldifDirectory.exists()) { if (! ldifDirectory.isDirectory()) { // The path specified as the LDIF directory exists, but isn't a // directory. This is probably a mistake, and we should at least log // a warning message. logError(WARN_LDIF_CONNHANDLER_LDIF_DIRECTORY_NOT_DIRECTORY.get( ldifDirectory.getAbsolutePath(), configuration.dn().toString())); } } else { // The path specified as the LDIF directory doesn't exist. We should log // a warning message saying that we won't do anything until it's created. logError(WARN_LDIF_CONNHANDLER_LDIF_DIRECTORY_MISSING.get( ldifDirectory.getAbsolutePath(), configuration.dn().toString())); } this.currentConfig = configuration; currentConfig.addLDIFChangeListener(this); DirectoryConfig.registerAlertGenerator(this); conn = InternalClientConnection.getRootConnection(); } /** * {@inheritDoc} */ @Override() public void finalizeConnectionHandler(Message finalizeReason, boolean closeConnections) { stopRequested = true; for (int i=0; i < 5; i++) { if (isStopped) { return; } else { try { if ((connectionHandlerThread != null) && (connectionHandlerThread.isAlive())) { connectionHandlerThread.join(100); connectionHandlerThread.interrupt(); } else { return; } } catch (Exception e) {} } } } /** * {@inheritDoc} */ @Override() public String getConnectionHandlerName() { return "LDIF Connection Handler"; } /** * {@inheritDoc} */ @Override() public String getProtocol() { return "LDIF"; } /** * {@inheritDoc} */ @Override() public Collection<HostPort> getListeners() { // There are no listeners for this connection handler. return Collections.<HostPort>emptySet(); } /** * {@inheritDoc} */ @Override() public Collection<ClientConnection> getClientConnections() { // There are no client connections for this connection handler. return Collections.<ClientConnection>emptySet(); } /** * {@inheritDoc} */ @Override() public void run() { isStopped = false; connectionHandlerThread = Thread.currentThread(); try { while (! stopRequested) { try { long startTime = System.currentTimeMillis(); File dir = ldifDirectory; if (dir.exists() && dir.isDirectory()) { File[] ldifFiles = dir.listFiles(); if (ldifFiles != null) { for (File f : ldifFiles) { if (f.getName().endsWith(".ldif")) { processLDIFFile(f); } } } } else { if (debugEnabled()) { TRACER.debugInfo("LDIF connection handler directory " + dir.getAbsolutePath() + "doesn't exist or isn't a file"); } } if (! stopRequested) { long currentTime = System.currentTimeMillis(); long sleepTime = startTime + currentConfig.getPollInterval() - currentTime; if (sleepTime > 0) { try { Thread.sleep(sleepTime); } catch (InterruptedException ie) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, ie); } } } } } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } } } } finally { connectionHandlerThread = null; isStopped = true; } } /** * Processes the contents of the provided LDIF file. * * @param ldifFile The LDIF file to be processed. */ private void processLDIFFile(File ldifFile) { if (debugEnabled()) { TRACER.debugInfo("Beginning processing on LDIF file " + ldifFile.getAbsolutePath()); } boolean fullyProcessed = false; boolean errorEncountered = false; String inputPath = ldifFile.getAbsolutePath(); LDIFImportConfig importConfig = new LDIFImportConfig(inputPath); importConfig.setInvokeImportPlugins(false); importConfig.setValidateSchema(true); String outputPath = inputPath + ".applied." + TimeThread.getGMTTime(); if (new File(outputPath).exists()) { int i=2; while (true) { if (! new File(outputPath + "." + i).exists()) { outputPath = outputPath + "." + i; break; } i++; } } LDIFExportConfig exportConfig = new LDIFExportConfig(outputPath, ExistingFileBehavior.APPEND); if (debugEnabled()) { TRACER.debugInfo("Creating applied file " + outputPath); } LDIFReader reader = null; LDIFWriter writer = null; try { reader = new LDIFReader(importConfig); writer = new LDIFWriter(exportConfig); while (true) { ChangeRecordEntry changeRecord; try { changeRecord = reader.readChangeRecord(false); if (debugEnabled()) { TRACER.debugInfo("Read change record entry " + String.valueOf(changeRecord)); } } catch (LDIFException le) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, le); } errorEncountered = true; if (le.canContinueReading()) { Message m = ERR_LDIF_CONNHANDLER_CANNOT_READ_CHANGE_RECORD_NONFATAL.get( le.getMessageObject()); writer.writeComment(m, 78); continue; } else { Message m = ERR_LDIF_CONNHANDLER_CANNOT_READ_CHANGE_RECORD_FATAL.get( le.getMessageObject()); writer.writeComment(m, 78); DirectoryConfig.sendAlertNotification(this, ALERT_TYPE_LDIF_CONNHANDLER_PARSE_ERROR, m); break; } } Operation operation = null; if (changeRecord == null) { fullyProcessed = true; break; } if (changeRecord instanceof AddChangeRecordEntry) { operation = conn.processAdd((AddChangeRecordEntry) changeRecord); } else if (changeRecord instanceof DeleteChangeRecordEntry) { operation = conn.processDelete( (DeleteChangeRecordEntry) changeRecord); } else if (changeRecord instanceof ModifyChangeRecordEntry) { operation = conn.processModify( (ModifyChangeRecordEntry) changeRecord); } else if (changeRecord instanceof ModifyDNChangeRecordEntry) { operation = conn.processModifyDN( (ModifyDNChangeRecordEntry) changeRecord); } if (operation == null) { Message m = INFO_LDIF_CONNHANDLER_UNKNOWN_CHANGETYPE.get( changeRecord.getChangeOperationType().getLDIFChangeType()); writer.writeComment(m, 78); } else { if (debugEnabled()) { TRACER.debugInfo("Result Code: " + operation.getResultCode().toString()); } Message m = INFO_LDIF_CONNHANDLER_RESULT_CODE.get( operation.getResultCode().getIntValue(), operation.getResultCode().toString()); writer.writeComment(m, 78); MessageBuilder errorMessage = operation.getErrorMessage(); if ((errorMessage != null) && (errorMessage.length() > 0)) { m = INFO_LDIF_CONNHANDLER_ERROR_MESSAGE.get(errorMessage); writer.writeComment(m, 78); } DN matchedDN = operation.getMatchedDN(); if (matchedDN != null) { m = INFO_LDIF_CONNHANDLER_MATCHED_DN.get(matchedDN.toString()); writer.writeComment(m, 78); } List<String> referralURLs = operation.getReferralURLs(); if ((referralURLs != null) && (! referralURLs.isEmpty())) { for (String url : referralURLs) { m = INFO_LDIF_CONNHANDLER_REFERRAL_URL.get(url); writer.writeComment(m, 78); } } } writer.writeChangeRecord(changeRecord); } } catch (IOException ioe) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, ioe); } fullyProcessed = false; Message m = ERR_LDIF_CONNHANDLER_IO_ERROR.get(inputPath, getExceptionMessage(ioe)); logError(m); DirectoryConfig.sendAlertNotification(this, ALERT_TYPE_LDIF_CONNHANDLER_PARSE_ERROR, m); } finally { if (reader != null) { try { reader.close(); } catch (Exception e) {} } if (writer != null) { try { writer.close(); } catch (Exception e) {} } } if (errorEncountered || (! fullyProcessed)) { String renamedPath = inputPath + ".errors-encountered." + TimeThread.getGMTTime(); if (new File(renamedPath).exists()) { int i=2; while (true) { if (! new File(renamedPath + "." + i).exists()) { renamedPath = renamedPath + "." + i; } i++; } } try { if (debugEnabled()) { TRACER.debugInfo("Renaming source file to " + renamedPath); } ldifFile.renameTo(new File(renamedPath)); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } Message m = ERR_LDIF_CONNHANDLER_CANNOT_RENAME.get(inputPath, renamedPath, getExceptionMessage(e)); logError(m); DirectoryConfig.sendAlertNotification(this, ALERT_TYPE_LDIF_CONNHANDLER_IO_ERROR, m); } } else { try { if (debugEnabled()) { TRACER.debugInfo("Deleting source file"); } ldifFile.delete(); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } Message m = ERR_LDIF_CONNHANDLER_CANNOT_DELETE.get(inputPath, getExceptionMessage(e)); logError(m); DirectoryConfig.sendAlertNotification(this, ALERT_TYPE_LDIF_CONNHANDLER_IO_ERROR, m); } } } /** * {@inheritDoc} */ @Override() public void toString(StringBuilder buffer) { buffer.append("LDIFConnectionHandler(ldifDirectory=\""); buffer.append(ldifDirectory.getAbsolutePath()); buffer.append("\", pollInterval="); buffer.append(currentConfig.getPollInterval()); buffer.append("ms)"); } /** * {@inheritDoc} */ @Override() public boolean isConfigurationAcceptable(ConnectionHandlerCfg configuration, List<Message> unacceptableReasons) { LDIFConnectionHandlerCfg cfg = (LDIFConnectionHandlerCfg) configuration; return isConfigurationChangeAcceptable(cfg, unacceptableReasons); } /** * {@inheritDoc} */ public boolean isConfigurationChangeAcceptable( LDIFConnectionHandlerCfg configuration, List<Message> unacceptableReasons) { // The configuration should always be acceptable. return true; } /** * {@inheritDoc} */ public ConfigChangeResult applyConfigurationChange( LDIFConnectionHandlerCfg configuration) { // The only processing we need to do here is to get the LDIF directory and // create a File object from it. File newLDIFDirectory = new File(configuration.getLDIFDirectory()); this.ldifDirectory = newLDIFDirectory; currentConfig = configuration; return new ConfigChangeResult(ResultCode.SUCCESS, false); } /** * {@inheritDoc} */ public DN getComponentEntryDN() { return currentConfig.dn(); } /** * {@inheritDoc} */ public String getClassName() { return LDIFConnectionHandler.class.getName(); } /** * {@inheritDoc} */ public LinkedHashMap<String,String> getAlerts() { LinkedHashMap<String,String> alerts = new LinkedHashMap<String,String>(); alerts.put(ALERT_TYPE_LDIF_CONNHANDLER_PARSE_ERROR, ALERT_DESCRIPTION_LDIF_CONNHANDLER_PARSE_ERROR); alerts.put(ALERT_TYPE_LDIF_CONNHANDLER_IO_ERROR, ALERT_DESCRIPTION_LDIF_CONNHANDLER_IO_ERROR); return alerts; } } opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java
@@ -25,18 +25,9 @@ * Portions Copyright 2006-2007 Sun Microsystems, Inc. */ package org.opends.server.protocols.internal; import org.opends.messages.Message; import static org.opends.server.config.ConfigConstants.*; import static org.opends.server.loggers.ErrorLogger.logError; import static org.opends.server.loggers.debug.DebugLogger.*; import static org.opends.messages.ProtocolMessages.*; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.StaticUtils.getExceptionMessage; import java.net.InetAddress; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -49,6 +40,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.opends.messages.Message; import org.opends.server.api.ClientConnection; import org.opends.server.api.ConnectionHandler; import org.opends.server.api.ConnectionSecurityProvider; @@ -71,8 +63,6 @@ import org.opends.server.types.DirectoryException; import org.opends.server.types.DisconnectReason; import org.opends.server.types.Entry; import org.opends.server.types.IntermediateResponse; import org.opends.server.types.LDAPException; import org.opends.server.types.Modification; @@ -88,6 +78,17 @@ import org.opends.server.types.SearchResultEntry; import org.opends.server.types.SearchResultReference; import org.opends.server.types.SearchScope; import org.opends.server.util.AddChangeRecordEntry; import org.opends.server.util.DeleteChangeRecordEntry; import org.opends.server.util.ModifyChangeRecordEntry; import org.opends.server.util.ModifyDNChangeRecordEntry; import static org.opends.messages.ProtocolMessages.*; import static org.opends.server.config.ConfigConstants.*; import static org.opends.server.loggers.ErrorLogger.logError; import static org.opends.server.loggers.debug.DebugLogger.*; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.StaticUtils.*; @@ -933,6 +934,54 @@ /** * Processes an internal add operation based on the provided add * change record entry. * * @param addRecord The add change record entry to be processed. * * @return A reference to the add operation that was processed and * contains information about the result of the processing. */ public AddOperation processAdd(AddChangeRecordEntry addRecord) { LinkedHashMap<ObjectClass,String> objectClasses = new LinkedHashMap<ObjectClass,String>(); LinkedHashMap<AttributeType,List<Attribute>> userAttrs = new LinkedHashMap<AttributeType,List<Attribute>>(); LinkedHashMap<AttributeType,List<Attribute>> opAttrs = new LinkedHashMap<AttributeType,List<Attribute>>(); Entry e = new Entry(addRecord.getDN(), objectClasses, userAttrs, opAttrs); ArrayList<AttributeValue> duplicateValues = new ArrayList<AttributeValue>(); for (Attribute a : addRecord.getAttributes()) { if (a.getAttributeType().isObjectClassType()) { for (AttributeValue v : a.getValues()) { String ocName = v.getStringValue(); String lowerName = toLowerCase(ocName); ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true); objectClasses.put(oc, ocName); } } else { e.addAttribute(a, duplicateValues); } } return processAdd(addRecord.getDN(), objectClasses, userAttrs, opAttrs); } /** * Processes an internal bind operation with the provided * information. Note that regardless of whether the bind is * successful, the authentication state for this internal connection @@ -1224,6 +1273,25 @@ /** * Processes an internal delete operation with the provided * information. * * @param deleteRecord The delete change record entry to be * processed. * * @return A reference to the delete operation that was processed * and contains information about the result of the * processing. */ public DeleteOperation processDelete( DeleteChangeRecordEntry deleteRecord) { return processDelete(deleteRecord.getDN()); } /** * Processes an internal extended operation with the provided * information. * @@ -1332,6 +1400,26 @@ /** * Processes an internal modify operation with the provided * information. * * @param modifyRecord The modify change record entry with * information about the changes to perform. * * @return A reference to the modify operation that was processed * and contains information about the result of the * processing. */ public ModifyOperation processModify( ModifyChangeRecordEntry modifyRecord) { return processModify(modifyRecord.getDN().toString(), modifyRecord.getModifications()); } /** * Processes an internal modify DN operation with the provided * information. * @@ -1498,6 +1586,29 @@ /** * Processes an internal modify DN operation with the provided * information. * * @param modifyDNRecord The modify DN change record entry with * information about the processing to * perform. * * @return A reference to the modify DN operation that was * processed and contains information about the result of * the processing. */ public ModifyDNOperation processModifyDN( ModifyDNChangeRecordEntry modifyDNRecord) { return processModifyDN(modifyDNRecord.getDN(), modifyDNRecord.getNewRDN(), modifyDNRecord.deleteOldRDN(), modifyDNRecord.getNewSuperiorDN()); } /** * Processes an internal search operation with the provided * information. It will not dereference any aliases, will not * request a size or time limit, and will retrieve all user opends/src/server/org/opends/server/types/ModificationType.java
@@ -44,7 +44,7 @@ * The modification type that indicates that the associated * attribute values should be added to the entry. */ ADD(0), ADD(0, "add"), @@ -52,7 +52,7 @@ * The modification type that indicates that the associated * attribute or set of values should be removed from the entry. */ DELETE(1), DELETE(1, "delete"), @@ -61,7 +61,7 @@ * attribute should be used to replace any existing values for that * attribute in the entry. */ REPLACE(2), REPLACE(2, "replace"), @@ -70,23 +70,30 @@ * associated attribute should be incremented by a specified amount * as defined in RFC 4525. */ INCREMENT(3); INCREMENT(3, "increment"); // The integer value for this modification type. private int intValue; // The name of this modification type as it should appear in LDIF // records. private String ldifName; /** * Creates a new modification type with the provided integer value. * * @param intValue The integer value for this modification type. * @param ldifName The name of this modification type as it should * appear in LDIF records. */ private ModificationType(int intValue) private ModificationType(int intValue, String ldifName) { this.intValue = intValue; this.ldifName = ldifName; } @@ -104,9 +111,23 @@ /** * Retrieves a string representation of this authentication type. * Retrieves the name of this modification type as it should appear * in LDIF records. * * @return A string representation of this authentication type. * @return The name of this modification type as it should appear * in LDIF records. */ public String getLDIFName() { return ldifName; } /** * Retrieves a string representation of this modification type. * * @return A string representation of this modification type. */ public String toString() { opends/src/server/org/opends/server/util/AddChangeRecordEntry.java
@@ -30,6 +30,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -114,5 +115,31 @@ return Collections.unmodifiableList(attributes); } /** * {@inheritDoc} */ @Override() public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append("AddChangeRecordEntry(dn=\""); buffer.append(String.valueOf(getDN())); buffer.append("\", attrs={"); Iterator<Attribute> iterator = attributes.iterator(); while (iterator.hasNext()) { buffer.append(iterator.next().getName()); if (iterator.hasNext()) { buffer.append(", "); } } buffer.append("})"); return buffer.toString(); } } opends/src/server/org/opends/server/util/ChangeOperationType.java
@@ -41,40 +41,51 @@ /** * The change type for add operations. */ ADD("ADD"), ADD("ADD", "add"), /** * The change type for delete operations. */ DELETE("DELETE"), DELETE("DELETE", "delete"), /** * The change type for modify operations. */ MODIFY("MODIFY"), MODIFY("MODIFY", "modify"), /** * The change type for modify DN operations. */ MODIFY_DN("MODIFY_DN"); MODIFY_DN("MODIFY_DN", "moddn"); // The name of this change type as it should appear in the "changetype" field // in LDIF records. private String ldifChangeType; // The user-friendly name given to this change type. private String type; /** * Creates a change type with the given string value. * * @param type The string value for this change type. * @param ldifChangeType The change type as it should appear in the * "changetype" field in LDIF records. */ private ChangeOperationType(String type) private ChangeOperationType(String type, String ldifChangeType) { this.type = type; this.ldifChangeType = ldifChangeType; } @@ -90,6 +101,19 @@ /** * Retrieves the name of the change type as it should appear in LDIF * "changetype" records. * * @return The name of the change type as it should appear in LDIF * "changetype" records. */ public String getLDIFChangeType() { return ldifChangeType; } /** * Retrieves a string representation of this type. * * @return A string representation of this type. opends/src/server/org/opends/server/util/ChangeRecordEntry.java
@@ -79,5 +79,14 @@ * @return The name of the change operation type. */ public abstract ChangeOperationType getChangeOperationType(); /** * Retrieves a string representation of this change record entry. * * @return A string representation of this change record entry. */ public abstract String toString(); } opends/src/server/org/opends/server/util/DeleteChangeRecordEntry.java
@@ -65,5 +65,21 @@ { return ChangeOperationType.DELETE; } /** * {@inheritDoc} */ @Override() public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append("DeleteChangeRecordEntry(dn=\""); buffer.append(String.valueOf(getDN())); buffer.append("\")"); return buffer.toString(); } } opends/src/server/org/opends/server/util/LDIFWriter.java
@@ -25,7 +25,6 @@ * Portions Copyright 2006-2007 Sun Microsystems, Inc. */ package org.opends.server.util; import org.opends.messages.Message; @@ -36,18 +35,22 @@ import java.util.regex.Pattern; import java.util.Collection; import org.opends.messages.Message; import org.opends.server.protocols.asn1.ASN1OctetString; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.types.Attribute; import org.opends.server.types.AttributeType; import org.opends.server.types.AttributeValue; import org.opends.server.types.DebugLogLevel; import org.opends.server.types.DN; import org.opends.server.types.Entry; import org.opends.server.types.LDIFExportConfig; import org.opends.server.types.Modification; import org.opends.server.types.RawAttribute; import org.opends.server.types.RawModification; import org.opends.server.types.RDN; import static org.opends.server.loggers.debug.DebugLogger.*; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.types.DebugLogLevel; import static org.opends.server.util.StaticUtils.*; import static org.opends.server.util.Validator.*; @@ -258,6 +261,155 @@ /** * Writes a change record entry for the provided change record. * * @param changeRecord The change record entry to be written. * * @throws IOException If a problem occurs while writing the change record. */ public void writeChangeRecord(ChangeRecordEntry changeRecord) throws IOException { ensureNotNull(changeRecord); // Get the information necessary to write the LDIF. BufferedWriter writer = exportConfig.getWriter(); int wrapColumn = exportConfig.getWrapColumn(); boolean wrapLines = (wrapColumn > 1); // First, write the DN. StringBuilder dnLine = new StringBuilder(); dnLine.append("dn"); appendLDIFSeparatorAndValue(dnLine, getBytes(changeRecord.getDN().toString())); writeLDIFLine(dnLine, writer, wrapLines, wrapColumn); // Figure out what type of change it is and act accordingly. if (changeRecord instanceof AddChangeRecordEntry) { StringBuilder changeTypeLine = new StringBuilder("changetype: add"); writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn); AddChangeRecordEntry addRecord = (AddChangeRecordEntry) changeRecord; for (Attribute a : addRecord.getAttributes()) { for (AttributeValue v : a.getValues()) { StringBuilder line = new StringBuilder(); line.append(a.getNameWithOptions()); String stringValue = v.getStringValue(); if (needsBase64Encoding(stringValue)) { line.append(":: "); line.append(Base64.encode(v.getValueBytes())); } else { line.append(": "); line.append(stringValue); } writeLDIFLine(line, writer, wrapLines, wrapColumn); } } } else if (changeRecord instanceof DeleteChangeRecordEntry) { StringBuilder changeTypeLine = new StringBuilder("changetype: delete"); writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn); } else if (changeRecord instanceof ModifyChangeRecordEntry) { StringBuilder changeTypeLine = new StringBuilder("changetype: modify"); writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn); ModifyChangeRecordEntry modifyRecord = (ModifyChangeRecordEntry) changeRecord; List<RawModification> mods = modifyRecord.getModifications(); Iterator<RawModification> iterator = mods.iterator(); while (iterator.hasNext()) { RawModification m = iterator.next(); RawAttribute a = m.getAttribute(); String attrName = a.getAttributeType(); StringBuilder modTypeLine = new StringBuilder(); modTypeLine.append(m.getModificationType().getLDIFName()); modTypeLine.append(": "); modTypeLine.append(attrName); writeLDIFLine(modTypeLine, writer, wrapLines, wrapColumn); for (ASN1OctetString s : a.getValues()) { StringBuilder valueLine = new StringBuilder(); String stringValue = s.stringValue(); valueLine.append(attrName); if (needsBase64Encoding(stringValue)) { valueLine.append(":: "); valueLine.append(Base64.encode(s.value())); } else { valueLine.append(": "); valueLine.append(stringValue); } writeLDIFLine(valueLine, writer, wrapLines, wrapColumn); } if (iterator.hasNext()) { StringBuilder dashLine = new StringBuilder("-"); writeLDIFLine(dashLine, writer, wrapLines, wrapColumn); } } } else if (changeRecord instanceof ModifyDNChangeRecordEntry) { StringBuilder changeTypeLine = new StringBuilder("changetype: moddn"); writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn); ModifyDNChangeRecordEntry modifyDNRecord = (ModifyDNChangeRecordEntry) changeRecord; StringBuilder newRDNLine = new StringBuilder(); newRDNLine.append("newrdn: "); modifyDNRecord.getNewRDN().toString(newRDNLine); writeLDIFLine(newRDNLine, writer, wrapLines, wrapColumn); StringBuilder deleteOldRDNLine = new StringBuilder(); deleteOldRDNLine.append("deleteoldrdn: "); if (modifyDNRecord.deleteOldRDN()) { deleteOldRDNLine.append("1"); } else { deleteOldRDNLine.append("0"); } writeLDIFLine(deleteOldRDNLine, writer, wrapLines, wrapColumn); DN newSuperiorDN = modifyDNRecord.getNewSuperiorDN(); if (newSuperiorDN != null) { StringBuilder newSuperiorLine = new StringBuilder(); newSuperiorLine.append("newsuperior: "); newSuperiorDN.toString(newSuperiorLine); writeLDIFLine(newSuperiorLine, writer, wrapLines, wrapColumn); } } // Make sure there is a blank line after the entry. writer.newLine(); } /** * Writes an add change record for the provided entry. No filtering will be * performed for this entry, nor will any export plugins be invoked. Further, * only the user attributes will be included. opends/src/server/org/opends/server/util/ModifyChangeRecordEntry.java
@@ -31,6 +31,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.opends.server.types.DN; @@ -100,5 +101,36 @@ { return ChangeOperationType.MODIFY; } /** * {@inheritDoc} */ @Override() public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append("ModifyChangeRecordEntry(dn=\""); buffer.append(String.valueOf(getDN())); buffer.append("\", mods={"); Iterator<RawModification> iterator = modifications.iterator(); while (iterator.hasNext()) { RawModification mod = iterator.next(); buffer.append(mod.getModificationType().getLDIFName()); buffer.append(" "); buffer.append(mod.getAttribute().getAttributeType()); if (iterator.hasNext()) { buffer.append(", "); } } buffer.append("})"); return buffer.toString(); } } opends/src/server/org/opends/server/util/ModifyDNChangeRecordEntry.java
@@ -127,5 +127,33 @@ { return ChangeOperationType.MODIFY_DN; } /** * {@inheritDoc} */ @Override() public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append("ModifyDNChangeRecordEntry(dn=\""); buffer.append(String.valueOf(getDN())); buffer.append("\", newRDN=\""); buffer.append(String.valueOf(newRDN)); buffer.append("\", deleteOldRDN="); buffer.append(deleteOldRDN); if (newSuperiorDN != null) { buffer.append(", newSuperior=\""); newSuperiorDN.toString(buffer); buffer.append("\""); } buffer.append(")"); return buffer.toString(); } } opends/src/server/org/opends/server/util/ServerConstants.java
@@ -1659,6 +1659,50 @@ /** * The description for the alert type that will be used for the alert * notification generated when the LDIF connection handler is unable to * process the contents of a file as valid LDIF. */ public static final String ALERT_DESCRIPTION_LDIF_CONNHANDLER_PARSE_ERROR = "This alert type will be used to provide notification that the " + "LDIF connection handler encountered an unrecoverable error while " + "attempting to parse an LDIF file."; /** * The alert type string that will be used for the alert notification * generated when the LDIF connection handler is unable to process the * contents of a file as valid LDIF. */ public static final String ALERT_TYPE_LDIF_CONNHANDLER_PARSE_ERROR = "org.opends.server.LDIFConnectionHandlerParseError"; /** * The description for the alert type that will be used for the alert * notification generated if an I/O error occurs while attempting to * read or write LDIF content. */ public static final String ALERT_DESCRIPTION_LDIF_CONNHANDLER_IO_ERROR = "This alert type will be used to provide notification that the " + "LDIF connection handler encountered an I/O error that prevented it " + "from completing its processing."; /** * The alert type string that will be used for the alert notification * generated if an I/O error occurs while attempting to read or write LDIF * content. */ public static final String ALERT_TYPE_LDIF_CONNHANDLER_IO_ERROR = "org.opends.server.LDIFConnectionHandlerIOError"; /** * The name of the default password storage scheme that will be used for new * passwords. */ opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/LDIFConnectionHandlerTestCase.java
New file @@ -0,0 +1,387 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2007 Sun Microsystems, Inc. */ package org.opends.server.protocols; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import org.opends.server.DirectoryServerTestCase; import org.opends.server.TestCaseUtils; import org.opends.server.api.ConnectionHandler; import org.opends.server.core.DirectoryServer; import static org.testng.Assert.*; /** * This class defines a set of tests for the * org.opends.server.protocols.LDIFConnectionHandler class. */ public class LDIFConnectionHandlerTestCase extends DirectoryServerTestCase { /** * Ensures that the server is running. * * @throws Exception If an unexpected problem occurs. */ @BeforeClass() public void startServer() throws Exception { TestCaseUtils.startServer(); } /** * Tests the ability of the LDIF connection handler to process a valid LDIF * file containing user data. * * @throws Exception If an unexpected problem occurs. */ @Test(groups = "slow") public void testValidUserLDIF() throws Exception { TestCaseUtils.initializeTestBackend(false); File tempDir = TestCaseUtils.createTemporaryDirectory("testValidUserLDIF"); TestCaseUtils.dsconfig( "set-connection-handler-prop", "--handler-name", "LDIF Connection Handler", "--set", "ldif-directory:" + tempDir.getAbsolutePath()); try { String path = TestCaseUtils.createTempFile( "dn: o=test", "changetype: add", "objectClass: top", "objectClass: organization", "o: test", "", "dn: cn=test,o=test", "changetype: add", "objectClass: top", "objectClass: device", "objectClass: extensibleObject", "cn: test", "", "dn: cn=test,o=test", "changetype: modify", "replace: description", "description: foo", "-", "add: givenName", "givenName: bar", "", "dn: cn=test,o=test", "changetype: moddn", "newrdn: uid=test", "deleteoldrdn: 0", "", "dn: uid=test,o=test", "changetype: delete"); File tempFile = new File(path); File newFile = new File(tempDir, "testValidUser.ldif"); assertTrue(tempFile.renameTo(newFile)); boolean found = false; long stopTime = System.currentTimeMillis() + 10000L; while (System.currentTimeMillis() < stopTime) { if (! newFile.exists()) { // The file should have been processed. Make sure that there is // a new file with the appropriate prefix. for (File f : tempDir.listFiles()) { found = f.getName().startsWith("testValidUser.ldif.applied."); if (! found) { System.err.println("Contents of file " + f.getName() + ":"); BufferedReader reader = new BufferedReader(new FileReader(f)); while (true) { String line = reader.readLine(); if (line == null) { break; } System.err.println(line); } throw new AssertionError("Found unexpected file " + f.getName() + " instead of testValidUser.ldif.applied.*"); } } break; } Thread.sleep(10); } assertTrue(found); } finally { TestCaseUtils.deleteDirectory(tempDir); } } /** * Tests the ability of the LDIF connection handler to process a valid LDIF * file containing configuration changes * * @throws Exception If an unexpected problem occurs. */ @Test(groups = "slow") public void testValidConfigLDIF() throws Exception { assertEquals(DirectoryServer.getSizeLimit(), 1000); TestCaseUtils.initializeTestBackend(false); File tempDir = TestCaseUtils.createTemporaryDirectory("testValidConfigLDIF"); TestCaseUtils.dsconfig( "set-connection-handler-prop", "--handler-name", "LDIF Connection Handler", "--set", "ldif-directory:" + tempDir.getAbsolutePath()); try { String path = TestCaseUtils.createTempFile( "dn: cn=config", "changetype: modify", "replace: ds-cfg-size-limit", "ds-cfg-size-limit: 100"); File tempFile = new File(path); File newFile = new File(tempDir, "testValidConfig.ldif"); assertTrue(tempFile.renameTo(newFile)); boolean found = false; long stopTime = System.currentTimeMillis() + 10000L; while (System.currentTimeMillis() < stopTime) { if (! newFile.exists()) { // The file should have been processed. Make sure that there is // a new file with the appropriate prefix. for (File f : tempDir.listFiles()) { found = f.getName().startsWith("testValidConfig.ldif.applied."); if (! found) { System.err.println("Contents of file " + f.getName() + ":"); BufferedReader reader = new BufferedReader(new FileReader(f)); while (true) { String line = reader.readLine(); if (line == null) { break; } System.err.println(line); } throw new AssertionError("Found unexpected file " + f.getName() + " instead of testValidConfig.ldif.applied.*"); } } break; } Thread.sleep(10); } assertTrue(found); assertEquals(DirectoryServer.getSizeLimit(), 100); } finally { TestCaseUtils.deleteDirectory(tempDir); TestCaseUtils.dsconfig( "set-global-configuration-prop", "--set", "size-limit:1000"); assertEquals(DirectoryServer.getSizeLimit(), 1000); } } /** * Tests the ability of the LDIF connection handler to properly deal with an * unparseable LDIF file. * * @throws Exception If an unexpected problem occurs. */ @Test(groups = "slow") public void testUnparseableLDIF() throws Exception { TestCaseUtils.initializeTestBackend(false); File tempDir = TestCaseUtils.createTemporaryDirectory("testValidUserLDIF"); TestCaseUtils.dsconfig( "set-connection-handler-prop", "--handler-name", "LDIF Connection Handler", "--set", "ldif-directory:" + tempDir.getAbsolutePath()); try { String path = TestCaseUtils.createTempFile( "unparseable"); File tempFile = new File(path); File newFile = new File(tempDir, "testUnparseable.ldif"); assertTrue(tempFile.renameTo(newFile)); boolean appliedFound = false; boolean errorsFound = false; long stopTime = System.currentTimeMillis() + 10000L; while (System.currentTimeMillis() < stopTime) { if (! newFile.exists()) { // The file should have been processed. Make sure that there is an // applied file, and that there is an errors file. for (File f : tempDir.listFiles()) { if (f.getName().startsWith("testUnparseable.ldif.applied.")) { appliedFound = true; } else if (f.getName().startsWith( "testUnparseable.ldif.errors-encountered.")) { errorsFound = true; } else { System.err.println("Contents of file " + f.getName() + ":"); BufferedReader reader = new BufferedReader(new FileReader(f)); while (true) { String line = reader.readLine(); if (line == null) { break; } System.err.println(line); } throw new AssertionError("Found unexpected file " + f.getName()); } } break; } Thread.sleep(10); } assertFalse(newFile.exists()); assertTrue(appliedFound); assertTrue(errorsFound); } finally { TestCaseUtils.deleteDirectory(tempDir); } } /** * Tests a number of methods that are part of the generic connection handler * API. * * @throws Exception If an unexpected problem occurs. */ @Test() public void testGenericConnectionHandlerMethods() throws Exception { // Get the connection handler from the server. LDIFConnectionHandler connHandler = null; for (ConnectionHandler handler : DirectoryServer.getConnectionHandlers()) { if (handler instanceof LDIFConnectionHandler) { assertNull(connHandler); connHandler = (LDIFConnectionHandler) handler; } } assertNotNull(connHandler); // Generic connection handler methods. assertNotNull(connHandler.getConnectionHandlerName()); assertNotNull(connHandler.getProtocol()); assertNotNull(connHandler.toString()); assertNotNull(connHandler.getListeners()); assertTrue(connHandler.getListeners().isEmpty()); assertNotNull(connHandler.getClientConnections()); assertTrue(connHandler.getClientConnections().isEmpty()); // Alert handler methods. assertNotNull(connHandler.getComponentEntryDN()); assertNotNull(connHandler.getClassName()); assertEquals(connHandler.getClassName(), connHandler.getClass().getName()); assertNotNull(connHandler.getAlerts()); assertFalse(connHandler.getAlerts().isEmpty()); } } opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestChangeRecordEntry.java
@@ -63,6 +63,17 @@ // Will not use. return null; } /** * {@inheritDoc} */ @Override() public String toString() { return "MyChangeRecordEntry()"; } } /**