/* * 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-2010 Sun Microsystems, Inc. */ package org.opends.quicksetup.util; import org.opends.messages.Message; import org.opends.messages.MessageBuilder; import static org.opends.messages.QuickSetupMessages.*; import org.opends.quicksetup.*; import org.opends.server.loggers.debug.TextDebugLogPublisher; import org.opends.server.loggers.debug.DebugLogger; import org.opends.server.loggers.TextErrorLogPublisher; import org.opends.server.loggers.TextWriter; import org.opends.server.loggers.ErrorLogger; import org.opends.server.loggers.TextAccessLogPublisher; import org.opends.server.loggers.AccessLogger; import org.opends.server.types.Modification; import org.opends.server.types.ResultCode; import org.opends.server.types.ByteString; import org.opends.server.types.InitializationException; import org.opends.server.types.Attribute; import org.opends.server.types.RawAttribute; import org.opends.server.types.SearchScope; import org.opends.server.types.SearchFilter; import org.opends.server.types.SearchResultEntry; import org.opends.server.api.DebugLogPublisher; import org.opends.server.api.ErrorLogPublisher; import org.opends.server.api.AccessLogPublisher; import org.opends.server.util.LDIFException; import org.opends.server.util.ModifyChangeRecordEntry; import org.opends.server.util.ChangeRecordEntry; import org.opends.server.util.AddChangeRecordEntry; import org.opends.server.protocols.internal.InternalClientConnection; import org.opends.server.protocols.internal.InternalSearchOperation; import org.opends.server.protocols.ldap.LDAPAttribute; import org.opends.server.core.ModifyOperation; import org.opends.server.core.DirectoryServer; import org.opends.server.core.AddOperation; import org.opends.server.core.DeleteOperation; import org.opends.server.core.CompareOperation; import org.opends.server.config.ConfigException; import java.util.logging.Logger; import java.util.logging.Level; import java.util.List; import java.util.ArrayList; import java.util.LinkedList; import java.io.IOException; /** * Class used to manipulate an OpenDS server in the same JVM process as * the client class. */ public class InProcessServerController { static private final Logger LOG = Logger.getLogger(InProcessServerController.class.getName()); /** * Indicates that the server has already been started once and that a * restart should happen instead. */ static private boolean serverHasBeenStarted = false; static private ErrorLogPublisher startupErrorPublisher; static private AccessLogPublisher startupAccessPublisher; static private DebugLogPublisher startupDebugPublisher; /** * Pushes messages published by the server loggers into OperationOutput. */ static private abstract class ServerControllerTextWriter implements TextWriter { private int bytesWritten = 0; private OperationOutput output = null; abstract void storeRecord(String record, OperationOutput output); ServerControllerTextWriter() { // do nothing } public void setOutput(OperationOutput ouput) { this.output = ouput; } public void writeRecord(String record) { if (record != null) { bytesWritten += bytesWritten; if (output != null) { storeRecord(record, output); } } } public void flush() { // do nothing; } public void shutdown() { // do nothing; } public long getBytesWritten() { return bytesWritten; } } static private ServerControllerTextWriter debugWriter = new ServerControllerTextWriter() { void storeRecord(String record, OperationOutput output) { LOG.log(Level.INFO, "server start (debug log): " + record); output.addDebugMessage(Message.raw(record)); }}; static private ServerControllerTextWriter errorWriter = new ServerControllerTextWriter() { void storeRecord(String record, OperationOutput output) { LOG.log(Level.INFO, "server start (error log): " + record); output.addErrorMessage(Message.raw(record)); } }; static private ServerControllerTextWriter accessWriter = new ServerControllerTextWriter() { void storeRecord(String record, OperationOutput output) { LOG.log(Level.INFO, "server start (access log): " + record); output.addAccessMessage(Message.raw(record)); } }; private Installation installation; /** * Creates a new instance that will operate on application's * installation. * @param installation representing the server instance to control * @throws IllegalStateException if the the version of the OpenDS code * running in this JVM is not the same version as the code whose bits * are stored in installation. */ public InProcessServerController(Installation installation) throws IllegalStateException { // Attempting to use DirectoryServer with a configuration file // for a different version of the server can cause problems for // the server at startup. BuildInformation installBi; BuildInformation currentBi; try { installBi = installation.getBuildInformation(); currentBi = BuildInformation.getCurrent(); } catch (Exception e) { throw new IllegalStateException("Failed to verify the build version of " + "the " + installation + " matches the currently executing " + "version."); } if (!currentBi.equals(installBi)) { throw new IllegalStateException("The build version of the " + "installation " + installation + " is " + installBi + " and does not match the currently executing version " + currentBi); } this.installation=installation; } /** * Starts the directory server within this process. * @param disableConnectionHandlers boolean that when true starts the * the server mode that is otherwise up and running but will not accept any * connections from external clients (i.e., does not create or initialize the * connection handlers). This could be useful, for example, in an upgrade mode * where it might be helpful to start the server but don't want it to appear * externally as if the server is online without connection handlers * listening. * @return OperationOutput object containing output from the start server * command invocation. * @throws org.opends.server.config.ConfigException * If there is a problem with the Directory Server * configuration that prevents a critical component * from being instantiated. * * @throws org.opends.server.types.InitializationException * If some other problem occurs while * attempting to initialize and start the * Directory Server. */ public OperationOutput startServer(boolean disableConnectionHandlers) throws InitializationException, ConfigException { LOG.log(Level.INFO, "Starting in process server with connection handlers " + (disableConnectionHandlers ? "disabled" : "enabled")); System.setProperty( "org.opends.server.DisableConnectionHandlers", disableConnectionHandlers ? "true" : "false"); return startServer(); } /** * Disables the server's connection handlers upon startup. The server * when started is otherwise up and running but will not accept any * connections from external clients (i.e., does not create or initialize the * connection handlers). This could be useful, for example, in an upgrade mode * where it might be helpful to start the server but don't want it to appear * externally as if the server is online without connection handlers * listening. * @param disable boolean that when true disables connection handlers when * the server is started. */ static public void disableConnectionHandlers(boolean disable) { System.setProperty( "org.opends.server.DisableConnectionHandlers", disable ? "true" : "false"); } /** * Disables the server's synchronization provider upon startup. The server * when started is otherwise up and running but will not accept any * synchronization message. This could be useful, for example, * in an upgrade mode where it might be helpful to start the server * but don't want it to appear externally. * @param disable boolean that when true disables synchronization provider * when the server is started. */ static public void disableSynchronization(boolean disable) { System.setProperty( "org.opends.server.DisableSynchronization", disable ? "true" : "false"); } /** * Disables the admin data synchronization upon startup. * @param disable boolean that when true disables connection handlers when * the server is started. */ static public void disableAdminDataSynchronization(boolean disable) { System.setProperty( "org.opends.server.DisableAdminDataSynchronization", disable ? "true" : "false"); } /** * Stops a server that had been running 'in process'. */ public void stopServer() { LOG.log(Level.INFO, "Shutting down in process server"); StandardOutputSuppressor.suppress(); try { DirectoryServer.shutDown(getClass().getName(), Message.raw("quicksetup requests shutdown")); // TODO: i18n // Note: this should not be necessary in the future when a // the shutdown method will not return until everything is // cleaned up. // Connection handlers are stopped and started asynchonously. // Give the connection handlers time to let go of any resources // before continuing. try { Thread.sleep(3000); } catch (InterruptedException e) { // do nothing; } } finally { StandardOutputSuppressor.unsuppress(); } } /** * Starts the OpenDS server in this process. * @return OperationOutput with the results of the operation. * @throws org.opends.server.config.ConfigException * If there is a problem with the Directory Server * configuration that prevents a critical component * from being instantiated. * * @throws org.opends.server.types.InitializationException * If some other problem occurs while * attempting to initialize and start the * Directory Server. */ public synchronized OperationOutput startServer() throws InitializationException, ConfigException { OperationOutput output = new OperationOutput(); setOutputForWriters(output); // Properties systemProperties = System.getProperties(); // systemProperties.list(System.out); StandardOutputSuppressor.suppress(); try { // The server's startServer method should not be called directly // more than once since it leave the server corrupted. Restart // is the correct choice in this case. if (!serverHasBeenStarted) { // Set the root of the directory server so that the server // code will be able to derive its filesystem paths etc. DirectoryServer.getEnvironmentConfig().setServerRoot( installation.getRootDirectory()); DirectoryServer directoryServer = DirectoryServer.getInstance(); // Bootstrap and start the Directory Server. LOG.log(Level.FINER, "Bootstrapping directory server"); directoryServer.bootstrapServer(); LOG.log(Level.FINER, "Initializing configuration"); String configClass = "org.opends.server.extensions.ConfigFileHandler"; String configPath = Utils.getPath( installation.getCurrentConfigurationFile()); directoryServer.initializeConfiguration(configClass, configPath); } else { LOG.log(Level.FINER, "Reinitializing the server"); DirectoryServer.reinitialize(); } // Must be done following bootstrap registerListenersForOuput(); LOG.log(Level.FINER, "Invoking server start"); // It is important to get a new instance after calling reinitalize() DirectoryServer directoryServer = DirectoryServer.getInstance(); directoryServer.startServer(); serverHasBeenStarted = true; // Note: this should not be necessary in the future. This // seems necessary currenty for the case in which shutdown // is called immediately afterward as is done by the upgrader. // Connection handlers are stopped and started asynchronously. // Give the connection handlers time to initialize before // continuing. try { Thread.sleep(3000); } catch (InterruptedException e) { // do nothing; } } finally { StandardOutputSuppressor.unsuppress(); setOutputForWriters(null); } return output; } /** * Applies modifications contained in an LDIF file to the server. * * @param cre changes to apply to the directory data * @throws IOException if there is an IO Error * @throws LDIFException if there is an LDIF error * @throws ApplicationException if there is an application specific error */ public void modify(ChangeRecordEntry cre) throws IOException, LDIFException, ApplicationException { InternalClientConnection cc = InternalClientConnection.getRootConnection(); ByteString dnByteString = ByteString.valueOf( cre.getDN().toString()); ResultCode rc; switch (cre.getChangeOperationType()) { case MODIFY: LOG.log(Level.INFO, "proparing to modify " + dnByteString); ModifyChangeRecordEntry mcre = (ModifyChangeRecordEntry) cre; ModifyOperation op = cc.processModify(dnByteString, mcre.getModifications()); rc = op.getResultCode(); if (rc.equals(ResultCode. SUCCESS)) { LOG.log(Level.INFO, "processed server modification " + modListToString(op.getModifications())); } else if (rc.equals( ResultCode. ATTRIBUTE_OR_VALUE_EXISTS)) { // ignore this error LOG.log(Level.INFO, "ignoring attribute that already exists: " + modListToString(op.getModifications())); } else if (rc.equals(ResultCode.NO_SUCH_ATTRIBUTE)) { // This can happen if for instance the old configuration was // changed so that the value of an attribute matches the default // value of the attribute in the new configuration. // Just log it and move on. LOG.log(Level.INFO, "Ignoring attribute not found: " + modListToString(op.getModifications())); } else { // report the error to the user MessageBuilder error = op.getErrorMessage(); throw new ApplicationException( ReturnCode.IMPORT_ERROR, INFO_ERROR_APPLY_LDIF_MODIFY.get(dnByteString.toString(), error != null ? error.toString() : ""), null); } break; case ADD: LOG.log(Level.INFO, "preparing to add " + dnByteString); AddChangeRecordEntry acre = (AddChangeRecordEntry) cre; List attrs = acre.getAttributes(); ArrayList rawAttrs = new ArrayList(attrs.size()); for (Attribute a : attrs) { rawAttrs.add(new LDAPAttribute(a)); } AddOperation addOp = cc.processAdd(dnByteString, rawAttrs); rc = addOp.getResultCode(); if (rc.equals(ResultCode.SUCCESS)) { LOG.log(Level.INFO, "processed server add " + addOp.getEntryDN()); } else if (rc.equals(ResultCode.ENTRY_ALREADY_EXISTS)) { // Compare the attributes with the existing entry to see if we // can ignore this add. boolean ignore = true; for (RawAttribute attr : rawAttrs) { ArrayList values = attr.getValues(); for (ByteString value : values) { CompareOperation compOp = cc.processCompare(dnByteString, attr.getAttributeType(), value); if (ResultCode.ASSERTION_FAILED.equals(compOp.getResultCode())) { ignore = false; break; } } } if (!ignore) { MessageBuilder error = addOp.getErrorMessage(); throw new ApplicationException( ReturnCode.IMPORT_ERROR, INFO_ERROR_APPLY_LDIF_ADD.get(dnByteString.toString(), error != null ? error.toString() : ""), null); } } else { boolean ignore = false; if (rc.equals(ResultCode.ENTRY_ALREADY_EXISTS)) { // The entry already exists. Compare the attributes with the // existing entry to see if we can ignore this add. try { InternalSearchOperation searchOp = cc.processSearch( cre.getDN(), SearchScope.BASE_OBJECT, SearchFilter.createFilterFromString( "objectclass=*")); LinkedList se = searchOp.getSearchEntries(); if (se.size() > 0) { SearchResultEntry e = se.get(0); List eAttrs = new ArrayList(); eAttrs.addAll(e.getAttributes()); eAttrs.add(e.getObjectClassAttribute()); if (compareUserAttrs(attrs, eAttrs)) { LOG.log(Level.INFO, "Ignoring failure to add " + dnByteString + " since the existing entry's " + "attributes are identical"); ignore = true; } } } catch (Exception e) { LOG.log(Level.INFO, "Error attempting to compare rejected add " + "entry with existing entry", e); } } if (!ignore) { MessageBuilder error = addOp.getErrorMessage(); throw new ApplicationException( ReturnCode.IMPORT_ERROR, INFO_ERROR_APPLY_LDIF_ADD.get(dnByteString.toString(), error != null ? error.toString() : ""), null); } } break; case DELETE: LOG.log(Level.INFO, "preparing to delete " + dnByteString); DeleteOperation delOp = cc.processDelete(dnByteString); rc = delOp.getResultCode(); if (rc.equals(ResultCode.SUCCESS)) { LOG.log(Level.INFO, "processed server delete " + delOp.getEntryDN()); } else { // report the error to the user MessageBuilder error = delOp.getErrorMessage(); throw new ApplicationException( ReturnCode.IMPORT_ERROR, INFO_ERROR_APPLY_LDIF_DELETE.get(dnByteString.toString(), error != null ? error.toString() : ""), null); } break; default: LOG.log(Level.SEVERE, "Unexpected record type " + cre.getClass()); throw new ApplicationException(ReturnCode.BUG, INFO_BUG_MSG.get(), null); } } private String modListToString( List modifications) { StringBuilder modsMsg = new StringBuilder(); for (int i = 0; i < modifications.size(); i++) { modsMsg.append(modifications.get(i).toString()); if (i < modifications.size() - 1) { modsMsg.append(" "); } } return modsMsg.toString(); } static private void setOutputForWriters(OperationOutput output) { debugWriter.setOutput(output); errorWriter.setOutput(output); accessWriter.setOutput(output); } static private void registerListenersForOuput() { try { startupDebugPublisher = TextDebugLogPublisher.getStartupTextDebugPublisher(debugWriter); DebugLogger.addDebugLogPublisher(startupDebugPublisher); startupErrorPublisher = TextErrorLogPublisher.getStartupTextErrorPublisher(errorWriter); ErrorLogger.addErrorLogPublisher(startupErrorPublisher); startupAccessPublisher = TextAccessLogPublisher.getStartupTextAccessPublisher( accessWriter, true); AccessLogger.addAccessLogPublisher(startupAccessPublisher); } catch (Exception e) { LOG.log(Level.INFO, "Error installing test log publishers: " + e.toString()); } } static private void unregisterListenersForOutput() { DebugLogger.removeDebugLogPublisher(startupDebugPublisher); ErrorLogger.removeErrorLogPublisher(startupErrorPublisher); AccessLogger.removeAccessLogPublisher(startupAccessPublisher); } /** * Compares two lists of attributes for equality. Any non-user attributes * are ignored during comparison. * @param l1 list of attributes * @param l2 list of attributes * @return boolean where true means the lists are equal */ private boolean compareUserAttrs(List l1, List l2) { return compareUserAttrsInternal(l1, l2) && compareUserAttrsInternal(l2, l1); } /** * Determines if all the user attributes in one list are present in another * list. * @param l1 list of attributes * @param l2 list of attributes * @return boolean where true means the lists are equal */ private boolean compareUserAttrsInternal(List l1, List l2) { for (Attribute l1Attr : l1) { if (l1Attr.getAttributeType().isOperational()) { continue; } // Locate the attribute in l2 Attribute l2Attr = null; String name = l1Attr.getName(); if (name != null) { for (Attribute tmpl2Attr : l2) { if (name.equals(tmpl2Attr.getName())) { l2Attr = tmpl2Attr; break; } } // If we found one them compare it if (l2Attr == null || (!l2Attr.getAttributeType().isOperational() && !l1Attr.equals(l2Attr))) { return false; } } else { return false; } } return true; } }