001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS.
026 */
027package org.opends.server.loggers;
028
029import static org.opends.messages.ConfigMessages.*;
030import static org.opends.server.util.StaticUtils.*;
031
032import java.util.Collection;
033import java.util.List;
034import java.util.concurrent.CopyOnWriteArrayList;
035
036import org.forgerock.i18n.LocalizableMessage;
037import org.forgerock.i18n.LocalizableMessageDescriptor.Arg3;
038import org.forgerock.i18n.slf4j.LocalizedLogger;
039import org.forgerock.opendj.config.server.ConfigChangeResult;
040import org.forgerock.opendj.config.server.ConfigException;
041import org.forgerock.opendj.ldap.ResultCode;
042import org.opends.server.admin.ClassPropertyDefinition;
043import org.opends.server.admin.server.ConfigurationAddListener;
044import org.opends.server.admin.server.ConfigurationChangeListener;
045import org.opends.server.admin.server.ConfigurationDeleteListener;
046import org.opends.server.admin.std.server.LogPublisherCfg;
047import org.opends.server.core.DirectoryServer;
048import org.opends.server.core.ServerContext;
049import org.opends.server.types.DN;
050import org.opends.server.types.InitializationException;
051import org.opends.server.util.StaticUtils;
052
053/**
054 * This class defines the wrapper that will invoke all registered loggers for
055 * each type of request received or response sent. If no log publishers are
056 * registered, messages will be directed to standard out.
057 *
058 * @param <P>
059 *          The type of the LogPublisher corresponding to this logger
060 * @param <C>
061 *          The type of the LogPublisherCfg corresponding to this logger
062 */
063public abstract class AbstractLogger
064    <P extends LogPublisher<C>, C extends LogPublisherCfg>
065    implements ConfigurationAddListener<C>, ConfigurationDeleteListener<C>,
066    ConfigurationChangeListener<C>
067{
068  /**
069   * The storage designed to store log publishers. It is helpful in abstracting
070   * away the methods used to manage the collection.
071   *
072   * @param <P>
073   *          The concrete {@link LogPublisher} type
074   * @param <C>
075   *          The concrete {@link LogPublisherCfg} type
076   */
077  protected static class LoggerStorage<P extends LogPublisher<C>,
078      C extends LogPublisherCfg>
079  {
080    /** Defined as public to allow subclasses of {@link AbstractLogger} to instantiate it. */
081    public LoggerStorage()
082    {
083      super();
084    }
085
086    /** The set of loggers that have been registered with the server. It will initially be empty. */
087    private Collection<P> logPublishers = new CopyOnWriteArrayList<>();
088
089    /**
090     * Add a log publisher to the logger.
091     *
092     * @param publisher
093     *          The log publisher to add.
094     */
095    public synchronized void addLogPublisher(P publisher)
096    {
097      logPublishers.add(publisher);
098    }
099
100    /**
101     * Remove a log publisher from the logger.
102     *
103     * @param publisher
104     *          The log publisher to remove.
105     * @return True if the log publisher is removed or false otherwise.
106     */
107    public synchronized boolean removeLogPublisher(P publisher)
108    {
109      boolean removed = logPublishers.remove(publisher);
110
111      if (removed)
112      {
113        publisher.close();
114      }
115
116      return removed;
117    }
118
119    /** Removes all existing log publishers from the logger. */
120    public synchronized void removeAllLogPublishers()
121    {
122      StaticUtils.close(logPublishers);
123      logPublishers.clear();
124    }
125
126    /**
127     * Returns the logPublishers.
128     *
129     * @return the collection of {@link LogPublisher}s
130     */
131    public Collection<P> getLogPublishers()
132    {
133      return logPublishers;
134    }
135  }
136
137  /**
138   * Returns the log publishers.
139   *
140   * @return the collection of {@link LogPublisher}s
141   */
142  protected abstract Collection<P> getLogPublishers();
143
144  /**
145   * Add a log publisher to the logger.
146   *
147   * @param publisher
148   *          The log publisher to add.
149   */
150  public abstract void addLogPublisher(P publisher);
151
152  /**
153   * Remove a log publisher from the logger.
154   *
155   * @param publisher
156   *          The log publisher to remove.
157   * @return True if the log publisher is removed or false otherwise.
158   */
159  public abstract boolean removeLogPublisher(P publisher);
160
161  /** Removes all existing log publishers from the logger. */
162  public abstract void removeAllLogPublishers();
163
164  /**
165   * Returns the java {@link ClassPropertyDefinition} for the current logger.
166   *
167   * @return the java {@link ClassPropertyDefinition} for the current logger.
168   */
169  protected abstract ClassPropertyDefinition getJavaClassPropertyDefinition();
170
171  private final Class<P> logPublisherClass;
172  private final Arg3<Object, Object, Object> invalidLoggerClassErrorMessage;
173  private ServerContext serverContext;
174
175  /**
176   * The constructor for this class.
177   *
178   * @param logPublisherClass
179   *          the log publisher class
180   * @param invalidLoggerClassErrorMessage
181   *          the error message to use if the logger class in invalid
182   */
183  AbstractLogger(
184      final Class<P> logPublisherClass,
185      final Arg3<Object, Object, Object>
186          invalidLoggerClassErrorMessage)
187  {
188    this.logPublisherClass = logPublisherClass;
189    this.invalidLoggerClassErrorMessage = invalidLoggerClassErrorMessage;
190  }
191
192  /**
193   * Initializes all the log publishers.
194   *
195   * @param configs The log publisher configurations.
196   * @param serverContext
197   *            The server context.
198   * @throws ConfigException
199   *           If an unrecoverable problem arises in the process of
200   *           performing the initialization as a result of the server
201   *           configuration.
202   * @throws InitializationException
203   *           If a problem occurs during initialization that is not
204   *           related to the server configuration.
205   */
206  public void initializeLogger(List<C> configs, ServerContext serverContext)
207      throws ConfigException, InitializationException
208  {
209    this.serverContext = serverContext;
210    for (C config : configs)
211    {
212      config.addChangeListener((ConfigurationChangeListener) this);
213
214      if (config.isEnabled())
215      {
216        final P logPublisher = serverContext.getCommonAudit().isCommonAuditConfig(config) ?
217            getLogPublisherForCommonAudit(config) : getLogPublisher(config);
218        addLogPublisher(logPublisher);
219      }
220    }
221  }
222
223  ServerContext getServerContext()
224  {
225    return serverContext;
226  }
227
228  @Override
229  public boolean isConfigurationAddAcceptable(C config, List<LocalizableMessage> unacceptableReasons)
230  {
231    return !config.isEnabled() || isJavaClassAcceptable(config, unacceptableReasons);
232  }
233
234  @Override
235  public boolean isConfigurationChangeAcceptable(C config, List<LocalizableMessage> unacceptableReasons)
236  {
237    return !config.isEnabled() || isJavaClassAcceptable(config, unacceptableReasons);
238  }
239
240  @Override
241  public ConfigChangeResult applyConfigurationAdd(C config)
242  {
243    final ConfigChangeResult ccr = applyConfigurationAdd0(config);
244    if (ccr.getResultCode() == ResultCode.SUCCESS)
245    {
246      config.addChangeListener((ConfigurationChangeListener) this);
247    }
248    return ccr;
249  }
250
251  private ConfigChangeResult applyConfigurationAdd0(C config)
252  {
253    final ConfigChangeResult ccr = new ConfigChangeResult();
254
255    if (config.isEnabled())
256    {
257      try
258      {
259        final P logPublisher = serverContext.getCommonAudit().isCommonAuditConfig(config) ?
260            getLogPublisherForCommonAudit(config) : getLogPublisher(config);
261        addLogPublisher(logPublisher);
262      }
263      catch(ConfigException e)
264      {
265        LocalizedLogger.getLoggerForThisClass().traceException(e);
266        ccr.addMessage(e.getMessageObject());
267        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
268      }
269      catch (Exception e)
270      {
271        LocalizedLogger.getLoggerForThisClass().traceException(e);
272        ccr.addMessage(ERR_CONFIG_LOGGER_CANNOT_CREATE_LOGGER.get(config.dn(), stackTraceToSingleLineString(e)));
273        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
274      }
275    }
276    return ccr;
277  }
278
279  private P findLogPublisher(DN dn)
280  {
281    for (P publisher : getLogPublishers())
282    {
283      if (publisher.getDN().equals(dn))
284      {
285        return publisher;
286      }
287    }
288    return null;
289  }
290
291  @Override
292  public ConfigChangeResult applyConfigurationChange(C config)
293  {
294    final ConfigChangeResult ccr = new ConfigChangeResult();
295
296    P logPublisher = findLogPublisher(config.dn());
297    if (logPublisher == null)
298    {
299      if (config.isEnabled())
300      {
301        // Needs to be added and enabled
302        return applyConfigurationAdd0(config);
303      }
304    }
305    else
306    {
307      if (config.isEnabled())
308      {
309        // Changes to the class name cannot be
310        // applied dynamically, so if the class name did change then
311        // indicate that administrative action is required for that
312        // change to take effect.
313        String className = config.getJavaClass();
314        if (!className.equals(logPublisher.getClass().getName()))
315        {
316          ccr.setAdminActionRequired(true);
317        }
318        try
319        {
320          CommonAudit commonAudit = serverContext.getCommonAudit();
321          if (commonAudit.isCommonAuditConfig(config))
322          {
323            commonAudit.addOrUpdatePublisher(config);
324          } // else the publisher is currently active, so we don't need to do anything.
325        }
326        catch (Exception e)
327        {
328          LocalizedLogger.getLoggerForThisClass().traceException(e);
329          ccr.addMessage(ERR_CONFIG_LOGGER_CANNOT_UPDATE_LOGGER.get(config.dn(), stackTraceToSingleLineString(e)));
330          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
331        }
332      }
333      else
334      {
335        // The publisher is being disabled so shut down and remove.
336        return applyConfigurationDelete(config);
337      }
338    }
339    return ccr;
340  }
341
342  @Override
343  public boolean isConfigurationDeleteAcceptable(C config, List<LocalizableMessage> unacceptableReasons)
344  {
345    return findLogPublisher(config.dn()) != null;
346  }
347
348  @Override
349  public ConfigChangeResult applyConfigurationDelete(C config)
350  {
351    final ConfigChangeResult ccr = new ConfigChangeResult();
352
353    P logPublisher = findLogPublisher(config.dn());
354    if(logPublisher != null)
355    {
356      removeLogPublisher(logPublisher);
357      try
358      {
359        CommonAudit commonAudit = serverContext.getCommonAudit();
360        if (commonAudit.isExistingCommonAuditConfig(config))
361        {
362          commonAudit.removePublisher(config);
363        }
364      }
365      catch (ConfigException e)
366      {
367        LocalizedLogger.getLoggerForThisClass().traceException(e);
368        ccr.addMessage(ERR_CONFIG_LOGGER_CANNOT_DELETE_LOGGER.get(config.dn(), stackTraceToSingleLineString(e)));
369        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
370      }
371    }
372    else
373    {
374      ccr.setResultCode(ResultCode.NO_SUCH_OBJECT);
375    }
376
377    return ccr;
378  }
379
380  private boolean isJavaClassAcceptable(C config,
381                                        List<LocalizableMessage> unacceptableReasons)
382  {
383    String className = config.getJavaClass();
384    ClassPropertyDefinition pd = getJavaClassPropertyDefinition();
385    try {
386      P publisher = pd.loadClass(className, logPublisherClass).newInstance();
387      return publisher.isConfigurationAcceptable(config, unacceptableReasons);
388    } catch (Exception e) {
389      unacceptableReasons.add(invalidLoggerClassErrorMessage.get(className, config.dn(), e));
390      return false;
391    }
392  }
393
394  private P getLogPublisher(C config) throws ConfigException
395  {
396    String className = config.getJavaClass();
397    ClassPropertyDefinition pd = getJavaClassPropertyDefinition();
398    try {
399      P logPublisher = pd.loadClass(className, logPublisherClass).newInstance();
400      logPublisher.initializeLogPublisher(config, serverContext);
401      return logPublisher;
402    }
403    catch (Exception e)
404    {
405      throw new ConfigException(
406          invalidLoggerClassErrorMessage.get(className, config.dn(), e), e);
407    }
408  }
409
410  private P getLogPublisherForCommonAudit(C config) throws InitializationException, ConfigException
411  {
412    CommonAudit commonAudit = serverContext.getCommonAudit();
413    commonAudit.addOrUpdatePublisher(config);
414    P logPublisher = getLogPublisher(config);
415    CommonAuditLogPublisher publisher = (CommonAuditLogPublisher) logPublisher;
416    publisher.setRequestHandler(commonAudit.getRequestHandler(config));
417    return logPublisher;
418  }
419}