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.ConfigException;
040import org.forgerock.opendj.ldap.ResultCode;
041import org.opends.server.admin.ClassPropertyDefinition;
042import org.opends.server.admin.server.ConfigurationAddListener;
043import org.opends.server.admin.server.ConfigurationChangeListener;
044import org.opends.server.admin.server.ConfigurationDeleteListener;
045import org.opends.server.admin.std.server.LogPublisherCfg;
046import org.opends.server.core.DirectoryServer;
047import org.opends.server.core.ServerContext;
048import org.forgerock.opendj.config.server.ConfigChangeResult;
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  /**
070   * The storage designed to store log publishers. It is helpful in abstracting
071   * away the methods used to manage the collection.
072   *
073   * @param <P>
074   *          The concrete {@link LogPublisher} type
075   * @param <C>
076   *          The concrete {@link LogPublisherCfg} type
077   */
078  protected static class LoggerStorage<P extends LogPublisher<C>,
079      C extends LogPublisherCfg>
080  {
081    /**
082     * Defined as public to allow subclasses of {@link AbstractLogger} to
083     * instantiate it.
084     */
085    public LoggerStorage()
086    {
087      super();
088    }
089
090    /**
091     * The set of loggers that have been registered with the server. It will
092     * initially be empty.
093     */
094    private Collection<P> logPublishers = new CopyOnWriteArrayList<>();
095
096
097    /**
098     * Add a log publisher to the logger.
099     *
100     * @param publisher
101     *          The log publisher to add.
102     */
103    public synchronized void addLogPublisher(P publisher)
104    {
105      logPublishers.add(publisher);
106    }
107
108    /**
109     * Remove a log publisher from the logger.
110     *
111     * @param publisher
112     *          The log publisher to remove.
113     * @return True if the log publisher is removed or false otherwise.
114     */
115    public synchronized boolean removeLogPublisher(P publisher)
116    {
117      boolean removed = logPublishers.remove(publisher);
118
119      if (removed)
120      {
121        publisher.close();
122      }
123
124      return removed;
125    }
126
127    /**
128     * Removes all existing log publishers from the logger.
129     */
130    public synchronized void removeAllLogPublishers()
131    {
132      StaticUtils.close(logPublishers);
133      logPublishers.clear();
134    }
135
136    /**
137     * Returns the logPublishers.
138     *
139     * @return the collection of {@link LogPublisher}s
140     */
141    public Collection<P> getLogPublishers()
142    {
143      return logPublishers;
144    }
145  }
146
147  /**
148   * Returns the log publishers.
149   *
150   * @return the collection of {@link LogPublisher}s
151   */
152  protected abstract Collection<P> getLogPublishers();
153
154  /**
155   * Add a log publisher to the logger.
156   *
157   * @param publisher
158   *          The log publisher to add.
159   */
160  public abstract void addLogPublisher(P publisher);
161
162  /**
163   * Remove a log publisher from the logger.
164   *
165   * @param publisher
166   *          The log publisher to remove.
167   * @return True if the log publisher is removed or false otherwise.
168   */
169  public abstract boolean removeLogPublisher(P publisher);
170
171  /**
172   * Removes all existing log publishers from the logger.
173   */
174  public abstract void removeAllLogPublishers();
175
176  /**
177   * Returns the java {@link ClassPropertyDefinition} for the current logger.
178   *
179   * @return the java {@link ClassPropertyDefinition} for the current logger.
180   */
181  protected abstract ClassPropertyDefinition getJavaClassPropertyDefinition();
182
183  private final Class<P> logPublisherClass;
184
185  private final Arg3<Object, Object, Object>
186      invalidLoggerClassErrorMessage;
187
188  ServerContext serverContext;
189
190  /**
191   * The constructor for this class.
192   *
193   * @param logPublisherClass
194   *          the log publisher class
195   * @param invalidLoggerClassErrorMessage
196   *          the error message to use if the logger class in invalid
197   */
198  AbstractLogger(
199      final Class<P> logPublisherClass,
200      final Arg3<Object, Object, Object>
201          invalidLoggerClassErrorMessage)
202  {
203    this.logPublisherClass = logPublisherClass;
204    this.invalidLoggerClassErrorMessage = invalidLoggerClassErrorMessage;
205  }
206
207  /**
208   * Initializes all the log publishers.
209   *
210   * @param configs The log publisher configurations.
211   * @param serverContext
212   *            The server context.
213   * @throws ConfigException
214   *           If an unrecoverable problem arises in the process of
215   *           performing the initialization as a result of the server
216   *           configuration.
217   * @throws InitializationException
218   *           If a problem occurs during initialization that is not
219   *           related to the server configuration.
220   */
221  public void initializeLogger(List<C> configs, ServerContext serverContext)
222      throws ConfigException, InitializationException
223  {
224    this.serverContext = serverContext;
225    for (C config : configs)
226    {
227      config.addChangeListener((ConfigurationChangeListener) this);
228
229      if(config.isEnabled())
230      {
231        addLogPublisher(getLogPublisher(config));
232      }
233    }
234  }
235
236  /** {@inheritDoc} */
237  @Override
238  public boolean isConfigurationAddAcceptable(C config,
239      List<LocalizableMessage> unacceptableReasons)
240  {
241    return !config.isEnabled() ||
242        isJavaClassAcceptable(config, unacceptableReasons);
243  }
244
245  /** {@inheritDoc} */
246  @Override
247  public boolean isConfigurationChangeAcceptable(C config,
248      List<LocalizableMessage> unacceptableReasons)
249  {
250    return !config.isEnabled() ||
251        isJavaClassAcceptable(config, unacceptableReasons);
252  }
253
254  /** {@inheritDoc} */
255  @Override
256  public ConfigChangeResult applyConfigurationAdd(C config)
257  {
258    final ConfigChangeResult ccr = new ConfigChangeResult();
259
260    config.addChangeListener((ConfigurationChangeListener) this);
261
262    if(config.isEnabled())
263    {
264      try
265      {
266        addLogPublisher(getLogPublisher(config));
267      }
268      catch(ConfigException e)
269      {
270        LocalizedLogger.getLoggerForThisClass().traceException(e);
271        ccr.addMessage(e.getMessageObject());
272        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
273      }
274      catch (Exception e)
275      {
276        LocalizedLogger.getLoggerForThisClass().traceException(e);
277        ccr.addMessage(ERR_CONFIG_LOGGER_CANNOT_CREATE_LOGGER.get(
278            config.dn(), stackTraceToSingleLineString(e)));
279        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
280      }
281    }
282    return ccr;
283  }
284
285  private P findLogPublisher(DN dn)
286  {
287    for (P publisher : getLogPublishers())
288    {
289      if (publisher.getDN().equals(dn))
290      {
291        return publisher;
292      }
293    }
294    return null;
295  }
296
297  /** {@inheritDoc} */
298  @Override
299  public ConfigChangeResult applyConfigurationChange(C config)
300  {
301    final ConfigChangeResult ccr = new ConfigChangeResult();
302
303    P logPublisher = findLogPublisher(config.dn());
304    if(logPublisher == null)
305    {
306      if(config.isEnabled())
307      {
308        // Needs to be added and enabled.
309        return applyConfigurationAdd(config);
310      }
311    }
312    else
313    {
314      if(config.isEnabled())
315      {
316        // The publisher is currently active, so we don't need to do anything.
317        // Changes to the class name cannot be
318        // applied dynamically, so if the class name did change then
319        // indicate that administrative action is required for that
320        // change to take effect.
321        String className = config.getJavaClass();
322        if(!className.equals(logPublisher.getClass().getName()))
323        {
324          ccr.setAdminActionRequired(true);
325        }
326      }
327      else
328      {
329        // The publisher is being disabled so shut down and remove.
330        removeLogPublisher(logPublisher);
331      }
332    }
333
334    return ccr;
335  }
336
337  /** {@inheritDoc} */
338  @Override
339  public boolean isConfigurationDeleteAcceptable(C config,
340      List<LocalizableMessage> unacceptableReasons)
341  {
342    return findLogPublisher(config.dn()) != null;
343  }
344
345  /** {@inheritDoc} */
346  @Override
347  public ConfigChangeResult applyConfigurationDelete(C config)
348  {
349    final ConfigChangeResult ccr = new ConfigChangeResult();
350
351    P logPublisher = findLogPublisher(config.dn());
352    if(logPublisher != null)
353    {
354      removeLogPublisher(logPublisher);
355    }
356    else
357    {
358      ccr.setResultCode(ResultCode.NO_SUCH_OBJECT);
359    }
360
361    return ccr;
362  }
363
364  private boolean isJavaClassAcceptable(C config,
365                                        List<LocalizableMessage> unacceptableReasons)
366  {
367    String className = config.getJavaClass();
368    ClassPropertyDefinition pd = getJavaClassPropertyDefinition();
369    try {
370      P publisher = pd.loadClass(className, logPublisherClass).newInstance();
371      return publisher.isConfigurationAcceptable(config, unacceptableReasons);
372    } catch (Exception e) {
373      unacceptableReasons.add(invalidLoggerClassErrorMessage.get(className, config.dn(), e));
374      return false;
375    }
376  }
377
378  private P getLogPublisher(C config) throws ConfigException
379  {
380    String className = config.getJavaClass();
381    ClassPropertyDefinition pd = getJavaClassPropertyDefinition();
382    try {
383      P logPublisher = pd.loadClass(className, logPublisherClass).newInstance();
384      logPublisher.initializeLogPublisher(config, serverContext);
385      return logPublisher;
386    }
387    catch (Exception e)
388    {
389      throw new ConfigException(
390          invalidLoggerClassErrorMessage.get(className, config.dn(), e), e);
391    }
392  }
393
394}