/* * 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 * docs/licenses/cddl.txt * or http://www.opensource.org/licenses/cddl1.php. * 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 * docs/licenses/cddl.txt. 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 2010-2023 Ping Identity Corporation */ package com.unboundid.directory.sdk.examples.groovy; import java.io.File; import java.io.FileWriter; import java.io.PrintWriter; import java.util.Date; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.unboundid.directory.sdk.http.config.HTTPOperationLoggerConfig; import com.unboundid.directory.sdk.http.scripting.ScriptedHTTPOperationLogger; import com.unboundid.directory.sdk.http.types.HTTPServerContext; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.ResultCode; import com.unboundid.util.StaticUtils; import com.unboundid.util.args.ArgumentException; import com.unboundid.util.args.ArgumentParser; import com.unboundid.util.args.FileArgument; /** * This class provides a simple example of a scripted HTTP operation logger that * will append a message to a specified file for any type of operation. It * takes a single configuration argument: * <UL> * <LI>log-file -- The path to the log file that will be written. This must * be provided.</LI> * </UL> */ public final class ExampleScriptedHTTPOperationLogger extends ScriptedHTTPOperationLogger { /** * The name of the argument that will be used for the argument used to specify * the path to the log file. */ private static final String ARG_NAME_LOG_FILE = "log-file"; // The path to the log file to be written. private volatile File logFile; // The server context for the server in which this extension is running. private HTTPServerContext serverContext; // The lock that will be used to synchronize logging activity. private final Object loggerLock; // The print writer that will be used to actually write the log messages. private volatile PrintWriter writer; /** * Creates a new instance of this HTTP operation logger. All HTTP operation * logger implementations must include a default constructor, but any * initialization should generally be done in the * {@code initializeHTTPOperationLogger} method. */ public ExampleScriptedHTTPOperationLogger() { loggerLock = new Object(); } /** * Updates the provided argument parser to define any configuration arguments * which may be used by this HTTP operation logger. The argument parser may * also be updated to define relationships between arguments (e.g., to specify * required, exclusive, or dependent argument sets). * * @param parser The argument parser to be updated with the configuration * arguments which may be used by this HTTP operation logger. * * @throws ArgumentException If a problem is encountered while updating the * provided argument parser. */ @Override() public void defineConfigArguments(final ArgumentParser parser) throws ArgumentException { // Add an argument that allows you to specify the path to the log file. Character shortIdentifier = null; String longIdentifier = ARG_NAME_LOG_FILE; boolean required = true; int maxOccurrences = 1; String placeholder = "{path}"; String description = "The path to the log file to be written. " + "Non-absolute paths will be treated as relative to the server root."; boolean fileMustExist = false; boolean parentMustExist = true; boolean mustBeFile = true; boolean mustBeDirectory = false; parser.addArgument(new FileArgument(shortIdentifier, longIdentifier, required, maxOccurrences, placeholder, description, fileMustExist, parentMustExist, mustBeFile, mustBeDirectory)); } /** * Initializes this HTTP operation logger. * * @param serverContext A handle to the server context for the server in * which this extension is running. * @param config The general configuration for this HTTP operation * logger. * @param parser The argument parser which has been initialized from * the configuration for this HTTP operation logger. * * @throws LDAPException If a problem occurs while initializing this HTTP * operation logger. */ @Override() public void initializeHTTPOperationLogger( final HTTPServerContext serverContext, final HTTPOperationLoggerConfig config, final ArgumentParser parser) throws LDAPException { this.serverContext = serverContext; // Create the logger and open the log file. try { final FileArgument arg = (FileArgument) parser.getNamedArgument(ARG_NAME_LOG_FILE); logFile = arg.getValue(); writer = new PrintWriter(new FileWriter(logFile, true), true); } catch (final Exception e) { serverContext.debugCaught(e); throw new LDAPException(ResultCode.OTHER, "Unable to open file " + logFile.getAbsolutePath() + " for writing: " + StaticUtils.getExceptionMessage(e), e); } } /** * Indicates whether the configuration contained in the provided argument * parser represents a valid configuration for this extension. * * @param config The general configuration for this HTTP * operation logger. * @param parser The argument parser which has been initialized * with the proposed configuration. * @param unacceptableReasons A list that can be updated with reasons that * the proposed configuration is not acceptable. * * @return {@code true} if the proposed configuration is acceptable, or * {@code false} if not. */ @Override() public boolean isConfigurationAcceptable( final HTTPOperationLoggerConfig config, final ArgumentParser parser, final List<String> unacceptableReasons) { // The argument parser will handle all of the necessary validation, so // we don't need to do anything here. return true; } /** * Attempts to apply the configuration contained in the provided argument * parser. * * @param config The general configuration for this HTTP * operation logger. * @param parser The argument parser which has been * initialized with the new configuration. * @param adminActionsRequired A list that can be updated with information * about any administrative actions that may be * required before one or more of the * configuration changes will be applied. * @param messages A list that can be updated with information * about the result of applying the new * configuration. * * @return A result code that provides information about the result of * attempting to apply the configuration change. */ @Override() public ResultCode applyConfiguration(final HTTPOperationLoggerConfig config, final ArgumentParser parser, final List<String> adminActionsRequired, final List<String> messages) { // Get the path to the log file from the new config. final FileArgument arg = (FileArgument) parser.getNamedArgument(ARG_NAME_LOG_FILE); final File newFile = arg.getValue(); // If the log file path hasn't changed, then we don't need to do anything. if (newFile.equals(logFile)) { return ResultCode.SUCCESS; } // Create a print writer that can be used to write to the new log file. final PrintWriter newWriter; try { newWriter = new PrintWriter(new FileWriter(newFile, true), true); } catch (final Exception e) { serverContext.debugCaught(e); messages.add("Unable to open new log file " + newFile.getAbsolutePath() + " for writing: " + StaticUtils.getExceptionMessage(e)); return ResultCode.OTHER; } // Swap the new logger into place. final PrintWriter oldWriter; synchronized (loggerLock) { oldWriter = writer; writer = newWriter; logFile = newFile; } // Close the old logger and return success. oldWriter.close(); return ResultCode.SUCCESS; } /** * Performs any cleanup which may be necessary when this HTTP operation logger * is to be taken out of service. */ @Override() public void finalizeHTTPOperationLogger() { synchronized (loggerLock) { writer.close(); writer = null; logFile = null; } } /** * Logs information about a servlet request that has been received from the * client. * * @param request An object with information about the request received * from the client. * @param stateMap An empty map which may be updated to hold state * information that can be used to correlate information * between the request and response. The same map instance * will be passed to the {@link #logResponse} method. */ @Override() public void logRequest(final HttpServletRequest request, final Map<String,Object> stateMap) { // All of the processing for this logger will be done during the course of // handling the response. } /** * Logs information about a servlet response to be returned to the client. * * @param request An object with information about the request received * from the client. * @param response An object with information about the response to be * returned to the client. * @param stateMap A map containing state any information added while * processing the {@link #logRequest} method. */ @Override() public void logResponse(final HttpServletRequest request, final HttpServletResponse response, final Map<String,Object> stateMap) { final Long processingTimeMillis = (Long) stateMap.get(STATE_KEY_PROCESSING_TIME_MILLIS); final Long bytesSent = (Long) stateMap.get(STATE_KEY_RESPONSE_CONTENT_LENGTH); final StringBuilder buffer = new StringBuilder(); buffer.append(new Date().toString()); buffer.append(' '); buffer.append("request=\""); buffer.append(request.getMethod()); buffer.append(' '); buffer.append(request.getRequestURI()); if (request.getMethod().equals("GET")) { final String queryString = request.getQueryString(); if ((queryString != null) && (queryString.length() > 0)) { buffer.append('?'); buffer.append(queryString); } } buffer.append(' '); buffer.append(request.getProtocol()); buffer.append("\" statusCode="); buffer.append(response.getStatus()); buffer.append(" bytesSent="); buffer.append(bytesSent); buffer.append(" processingTimeMillis="); buffer.append(processingTimeMillis); synchronized (loggerLock) { if (writer == null) { // This should only happen if the logger has been shut down. return; } writer.println(buffer.toString()); writer.flush(); } } }