/* * 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 * * * Copyright 2008-2009 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.core.DirectoryServer; 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; // Help to not warn permanently and fullfill the log file // in debug mode. private boolean alreadyWarn = false; /** * 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; alreadyWarn = false; } /** * {@inheritDoc} */ @Override() public void initializeConnectionHandler(LDIFConnectionHandlerCfg configuration) { String ldifDirectoryPath = configuration.getLDIFDirectory(); ldifDirectory = new File(ldifDirectoryPath); // If we have a relative path to the instance, get the absolute one. if ( ! ldifDirectory.isAbsolute() ) { ldifDirectory = new File(DirectoryServer.getInstanceRoot() + File.separator + ldifDirectoryPath); } 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) { 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 (!alreadyWarn && debugEnabled()) { TRACER.debugInfo("LDIF connection handler directory " + dir.getAbsolutePath() + " doesn't exist or isn't a directory"); alreadyWarn = true; } } 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} */ @Override 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; } }