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