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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.loggers;
028    import org.opends.messages.Message;
029    
030    import java.util.concurrent.CopyOnWriteArrayList;
031    import java.util.List;
032    import java.util.ArrayList;
033    import java.lang.reflect.Method;
034    import java.lang.reflect.InvocationTargetException;
035    
036    import org.opends.server.api.DirectoryThread;
037    import org.opends.server.api.ErrorLogPublisher;
038    import org.opends.server.backends.task.Task;
039    import org.opends.server.loggers.debug.DebugTracer;
040    
041    import org.opends.server.types.*;
042    import org.opends.server.admin.std.server.ErrorLogPublisherCfg;
043    import org.opends.server.admin.std.meta.ErrorLogPublisherCfgDefn;
044    import org.opends.server.admin.server.ConfigurationAddListener;
045    import org.opends.server.admin.server.ConfigurationDeleteListener;
046    import org.opends.server.admin.server.ConfigurationChangeListener;
047    import org.opends.server.admin.ClassPropertyDefinition;
048    import org.opends.server.config.ConfigException;
049    import org.opends.server.core.DirectoryServer;
050    
051    import static org.opends.server.loggers.debug.DebugLogger.*;
052    import static org.opends.messages.ConfigMessages.*;
053    import static org.opends.server.util.StaticUtils.*;
054    /**
055     * This class defines the wrapper that will invoke all registered error loggers
056     * for each type of request received or response sent. If no error log
057     * publishers are registered, messages will be directed to standard out.
058     */
059    public class ErrorLogger implements
060        ConfigurationAddListener<ErrorLogPublisherCfg>,
061        ConfigurationDeleteListener<ErrorLogPublisherCfg>,
062        ConfigurationChangeListener<ErrorLogPublisherCfg>
063    {
064      /**
065       * The tracer object for the debug logger.
066       */
067      private static final DebugTracer TRACER = getTracer();
068    
069      // The set of error loggers that have been registered with the server. It
070      // will initially be empty.
071      private static CopyOnWriteArrayList<ErrorLogPublisher> errorPublishers =
072          new CopyOnWriteArrayList<ErrorLogPublisher>();
073    
074      // The singleton instance of this class for configuration purposes.
075      private static final ErrorLogger instance = new ErrorLogger();
076    
077      /**
078       * Retrieve the singleton instance of this class.
079       *
080       * @return The singleton instance of this logger.
081       */
082      public static ErrorLogger getInstance()
083      {
084        return instance;
085      }
086    
087      /**
088       * Add an error log publisher to the error logger.
089       *
090       * @param publisher The error log publisher to add.
091       */
092      public synchronized static void addErrorLogPublisher(
093          ErrorLogPublisher publisher)
094      {
095        errorPublishers.add(publisher);
096      }
097    
098      /**
099       * Remove an error log publisher from the error logger.
100       *
101       * @param publisher The error log publisher to remove.
102       * @return True if the error log publisher is removed or false otherwise.
103       */
104      public synchronized static boolean removeErrorLogPublisher(
105          ErrorLogPublisher publisher)
106      {
107        boolean removed = errorPublishers.remove(publisher);
108    
109        if(removed)
110        {
111          publisher.close();
112        }
113    
114        return removed;
115      }
116    
117      /**
118       * Removes all existing error log publishers from the logger.
119       */
120      public synchronized static void removeAllErrorLogPublishers()
121      {
122        for(ErrorLogPublisher publisher : errorPublishers)
123        {
124          publisher.close();
125        }
126    
127        errorPublishers.clear();
128      }
129    
130      /**
131       * Initializes all the error log publishers.
132       *
133       * @param configs The error log publisher configurations.
134       * @throws ConfigException
135       *           If an unrecoverable problem arises in the process of
136       *           performing the initialization as a result of the server
137       *           configuration.
138       * @throws InitializationException
139       *           If a problem occurs during initialization that is not
140       *           related to the server configuration.
141       */
142      public void initializeErrorLogger(List<ErrorLogPublisherCfg> configs)
143          throws ConfigException, InitializationException
144      {
145        for(ErrorLogPublisherCfg config : configs)
146        {
147          config.addErrorChangeListener(this);
148    
149          if(config.isEnabled())
150          {
151            ErrorLogPublisher errorLogPublisher = getErrorPublisher(config);
152    
153            addErrorLogPublisher(errorLogPublisher);
154          }
155        }
156      }
157    
158      /**
159       * {@inheritDoc}
160       */
161      public boolean isConfigurationAddAcceptable(ErrorLogPublisherCfg config,
162                                                  List<Message> unacceptableReasons)
163      {
164        return !config.isEnabled() ||
165            isJavaClassAcceptable(config, unacceptableReasons);
166      }
167    
168      /**
169       * {@inheritDoc}
170       */
171      public boolean isConfigurationChangeAcceptable(
172              ErrorLogPublisherCfg config,
173              List<Message> unacceptableReasons)
174      {
175        return !config.isEnabled() ||
176            isJavaClassAcceptable(config, unacceptableReasons);
177      }
178    
179      /**
180       * {@inheritDoc}
181       */
182      public ConfigChangeResult applyConfigurationAdd(ErrorLogPublisherCfg config)
183      {
184        // Default result code.
185        ResultCode resultCode = ResultCode.SUCCESS;
186        boolean adminActionRequired = false;
187        ArrayList<Message> messages = new ArrayList<Message>();
188    
189        config.addErrorChangeListener(this);
190    
191        if(config.isEnabled())
192        {
193          try
194          {
195            ErrorLogPublisher errorLogPublisher = getErrorPublisher(config);
196    
197            addErrorLogPublisher(errorLogPublisher);
198          }
199          catch(ConfigException e)
200          {
201            if (debugEnabled())
202            {
203              TRACER.debugCaught(DebugLogLevel.ERROR, e);
204            }
205            messages.add(e.getMessageObject());
206            resultCode = DirectoryServer.getServerErrorResultCode();
207          }
208          catch (Exception e)
209          {
210            if (debugEnabled())
211            {
212              TRACER.debugCaught(DebugLogLevel.ERROR, e);
213            }
214            messages.add(ERR_CONFIG_LOGGER_CANNOT_CREATE_LOGGER.get(
215                    String.valueOf(config.dn().toString()),
216                    stackTraceToSingleLineString(e)));
217            resultCode = DirectoryServer.getServerErrorResultCode();
218          }
219        }
220        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
221      }
222    
223      /**
224       * {@inheritDoc}
225       */
226      public ConfigChangeResult applyConfigurationChange(
227          ErrorLogPublisherCfg config)
228      {
229        // Default result code.
230        ResultCode resultCode = ResultCode.SUCCESS;
231        boolean adminActionRequired = false;
232        ArrayList<Message> messages = new ArrayList<Message>();
233    
234        DN dn = config.dn();
235    
236        ErrorLogPublisher errorLogPublisher = null;
237        for(ErrorLogPublisher publisher : errorPublishers)
238        {
239          if(publisher.getDN().equals(dn))
240          {
241            errorLogPublisher = publisher;
242            break;
243          }
244        }
245    
246        if(errorLogPublisher == null)
247        {
248          if(config.isEnabled())
249          {
250            // Needs to be added and enabled.
251            return applyConfigurationAdd(config);
252          }
253        }
254        else
255        {
256          if(config.isEnabled())
257          {
258            // The publisher is currently active, so we don't need to do anything.
259            // Changes to the class name cannot be
260            // applied dynamically, so if the class name did change then
261            // indicate that administrative action is required for that
262            // change to take effect.
263            String className = config.getJavaClass();
264            if(!className.equals(errorLogPublisher.getClass().getName()))
265            {
266              adminActionRequired = true;
267            }
268          }
269          else
270          {
271            // The publisher is being disabled so shut down and remove.
272            removeErrorLogPublisher(errorLogPublisher);
273          }
274        }
275    
276        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
277      }
278    
279      /**
280       * {@inheritDoc}
281       */
282      public boolean isConfigurationDeleteAcceptable(
283              ErrorLogPublisherCfg config,
284              List<Message> unacceptableReasons)
285      {
286        DN dn = config.dn();
287    
288        ErrorLogPublisher errorLogPublisher = null;
289        for(ErrorLogPublisher publisher : errorPublishers)
290        {
291          if(publisher.getDN().equals(dn))
292          {
293            errorLogPublisher = publisher;
294            break;
295          }
296        }
297    
298        return errorLogPublisher != null;
299      }
300    
301      /**
302       * {@inheritDoc}
303       */
304      public ConfigChangeResult applyConfigurationDelete(
305          ErrorLogPublisherCfg config)
306      {
307        // Default result code.
308        ResultCode resultCode = ResultCode.SUCCESS;
309        boolean adminActionRequired = false;
310    
311        ErrorLogPublisher errorLogPublisher = null;
312        for(ErrorLogPublisher publisher : errorPublishers)
313        {
314          if(publisher.getDN().equals(config.dn()))
315          {
316            errorLogPublisher = publisher;
317            break;
318          }
319        }
320    
321        if(errorLogPublisher != null)
322        {
323          removeErrorLogPublisher(errorLogPublisher);
324        }
325        else
326        {
327          resultCode = ResultCode.NO_SUCH_OBJECT;
328        }
329    
330        return new ConfigChangeResult(resultCode, adminActionRequired);
331      }
332    
333      private boolean isJavaClassAcceptable(ErrorLogPublisherCfg config,
334                                            List<Message> unacceptableReasons)
335      {
336        String className = config.getJavaClass();
337        ErrorLogPublisherCfgDefn d = ErrorLogPublisherCfgDefn.getInstance();
338        ClassPropertyDefinition pd =
339            d.getJavaClassPropertyDefinition();
340        // Load the class and cast it to a DebugLogPublisher.
341        ErrorLogPublisher publisher = null;
342        Class<? extends ErrorLogPublisher> theClass;
343        try {
344          theClass = pd.loadClass(className, ErrorLogPublisher.class);
345          publisher = theClass.newInstance();
346        } catch (Exception e) {
347          Message message = ERR_CONFIG_LOGGER_INVALID_ERROR_LOGGER_CLASS.get(
348                  className,
349                  config.dn().toString(),
350                  String.valueOf(e));
351          unacceptableReasons.add(message);
352          return false;
353        }
354        // Check that the implementation class implements the correct interface.
355        try {
356          // Determine the initialization method to use: it must take a
357          // single parameter which is the exact type of the configuration
358          // object.
359          Method method = theClass.getMethod("isConfigurationAcceptable",
360                                             ErrorLogPublisherCfg.class,
361                                             List.class);
362          Boolean acceptable = (Boolean) method.invoke(publisher, config,
363                                                       unacceptableReasons);
364    
365          if (! acceptable)
366          {
367            return false;
368          }
369        } catch (Exception e) {
370          Message message = ERR_CONFIG_LOGGER_INVALID_ERROR_LOGGER_CLASS.get(
371                  className,
372                  config.dn().toString(),
373                  String.valueOf(e));
374          unacceptableReasons.add(message);
375          return false;
376        }
377        // The class is valid as far as we can tell.
378        return true;
379      }
380    
381      private ErrorLogPublisher getErrorPublisher(ErrorLogPublisherCfg config)
382          throws ConfigException {
383        String className = config.getJavaClass();
384        ErrorLogPublisherCfgDefn d = ErrorLogPublisherCfgDefn.getInstance();
385        ClassPropertyDefinition pd =
386            d.getJavaClassPropertyDefinition();
387        // Load the class and cast it to a ErrorLogPublisher.
388        Class<? extends ErrorLogPublisher> theClass;
389        ErrorLogPublisher errorLogPublisher;
390        try {
391          theClass = pd.loadClass(className, ErrorLogPublisher.class);
392          errorLogPublisher = theClass.newInstance();
393    
394          // Determine the initialization method to use: it must take a
395          // single parameter which is the exact type of the configuration
396          // object.
397          Method method = theClass.getMethod("initializeErrorLogPublisher", config
398              .configurationClass());
399          method.invoke(errorLogPublisher, config);
400        }
401        catch (InvocationTargetException ite)
402        {
403          // Rethrow the exceptions thrown be the invoked method.
404          Throwable e = ite.getTargetException();
405          Message message = ERR_CONFIG_LOGGER_INVALID_ERROR_LOGGER_CLASS.get(
406              className, config.dn().toString(), stackTraceToSingleLineString(e));
407          throw new ConfigException(message, e);
408        }
409        catch (Exception e)
410        {
411          Message message = ERR_CONFIG_LOGGER_INVALID_ERROR_LOGGER_CLASS.get(
412              className, config.dn().toString(), String.valueOf(e));
413          throw new ConfigException(message, e);
414        }
415    
416        // The error publisher has been successfully initialized.
417        return errorLogPublisher;
418      }
419    
420    
421    
422      /**
423       * Writes a message to the error log using the provided information.
424       *
425       * @param  message   The message to be logged.
426       */
427      public static void logError(Message message)
428      {
429        for (ErrorLogPublisher publisher : errorPublishers)
430        {
431          publisher.logError(message);
432        }
433    
434        if (Thread.currentThread() instanceof DirectoryThread)
435        {
436          DirectoryThread thread = (DirectoryThread) Thread.currentThread();
437          Task task = thread.getAssociatedTask();
438          if (task != null)
439          {
440            task.addLogMessage(message);
441          }
442        }
443      }
444    }
445