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.core;
028    import org.opends.messages.Message;
029    
030    
031    
032    import java.util.ArrayList;
033    import java.util.List;
034    import java.util.concurrent.ConcurrentHashMap;
035    import java.lang.reflect.Method;
036    
037    import org.opends.server.admin.ClassPropertyDefinition;
038    import org.opends.server.admin.server.ConfigurationChangeListener;
039    import org.opends.server.admin.server.ConfigurationAddListener;
040    import org.opends.server.admin.server.ConfigurationDeleteListener;
041    import org.opends.server.admin.server.ServerManagementContext;
042    import org.opends.server.admin.std.server.ExtendedOperationHandlerCfg;
043    import org.opends.server.admin.std.server.RootCfg;
044    import org.opends.server.admin.std.meta.ExtendedOperationHandlerCfgDefn;
045    import org.opends.server.api.ExtendedOperationHandler;
046    import org.opends.server.config.ConfigException;
047    import org.opends.server.loggers.debug.DebugTracer;
048    import org.opends.server.types.ConfigChangeResult;
049    import org.opends.server.types.DebugLogLevel;
050    import org.opends.server.types.DN;
051    import org.opends.server.types.InitializationException;
052    import org.opends.server.types.ResultCode;
053    
054    import static org.opends.server.loggers.debug.DebugLogger.*;
055    import static org.opends.messages.ConfigMessages.*;
056    
057    import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
058    
059    
060    
061    /**
062     * This class defines a utility that will be used to manage the set of extended
063     * operation handlers defined in the Directory Server.  It will initialize the
064     * handlers when the server starts, and then will manage any additions,
065     * removals, or modifications of any extended operation handlers while the
066     * server is running.
067     */
068    public class ExtendedOperationConfigManager implements
069         ConfigurationChangeListener<ExtendedOperationHandlerCfg>,
070         ConfigurationAddListener<ExtendedOperationHandlerCfg>,
071         ConfigurationDeleteListener<ExtendedOperationHandlerCfg>
072    {
073      /**
074       * The tracer object for the debug logger.
075       */
076      private static final DebugTracer TRACER = getTracer();
077    
078    
079    
080      // A mapping between the DNs of the config entries and the associated extended
081      // operation handlers.
082      private ConcurrentHashMap<DN,ExtendedOperationHandler> handlers;
083    
084    
085    
086      /**
087       * Creates a new instance of this extended operation config manager.
088       */
089      public ExtendedOperationConfigManager()
090      {
091        handlers = new ConcurrentHashMap<DN,ExtendedOperationHandler>();
092      }
093    
094    
095    
096      /**
097       * Initializes all extended operation handlers currently defined in the
098       * Directory Server configuration.  This should only be called at Directory
099       * Server startup.
100       *
101       * @throws  ConfigException  If a configuration problem causes the extended
102       *                           operation handler initialization process to fail.
103       *
104       * @throws  InitializationException  If a problem occurs while initializing
105       *                                   the extended operation handler that is
106       *                                   not related to the server configuration.
107       */
108      public void initializeExtendedOperationHandlers()
109             throws ConfigException, InitializationException
110      {
111        // Create an internal server management context and retrieve
112        // the root configuration which has the extended operation handler relation.
113        ServerManagementContext context = ServerManagementContext.getInstance();
114        RootCfg root = context.getRootConfiguration();
115    
116        // Register add and delete listeners.
117        root.addExtendedOperationHandlerAddListener(this);
118        root.addExtendedOperationHandlerDeleteListener(this);
119    
120        // Initialize existing handlers.
121        for (String name : root.listExtendedOperationHandlers())
122        {
123          // Get the handler's configuration.
124          // This will decode and validate its properties.
125          ExtendedOperationHandlerCfg config =
126               root.getExtendedOperationHandler(name);
127    
128          // Register as a change listener for this handler so that we can be
129          // notified when it is disabled or enabled.
130          config.addChangeListener(this);
131    
132          // Ignore this handler if it is disabled.
133          if (config.isEnabled())
134          {
135            // Load the handler's implementation class and initialize it.
136            ExtendedOperationHandler handler = getHandler(config);
137    
138            // Put this handler in the hash map so that we will be able to find
139            // it if it is deleted or disabled.
140            handlers.put(config.dn(), handler);
141          }
142        }
143      }
144    
145      /**
146       * {@inheritDoc}
147       */
148      public ConfigChangeResult applyConfigurationDelete(
149           ExtendedOperationHandlerCfg configuration)
150      {
151        ResultCode resultCode          = ResultCode.SUCCESS;
152        boolean    adminActionRequired = false;
153    
154    
155        // See if the entry is registered as an extended operation handler.  If so,
156        // deregister it and finalize the handler.
157        ExtendedOperationHandler handler = handlers.remove(configuration.dn());
158        if (handler != null)
159        {
160          handler.finalizeExtendedOperationHandler();
161        }
162    
163    
164        return new ConfigChangeResult(resultCode, adminActionRequired);
165      }
166    
167      /**
168       * {@inheritDoc}
169       */
170      public boolean isConfigurationChangeAcceptable(
171           ExtendedOperationHandlerCfg configuration,
172           List<Message> unacceptableReasons)
173      {
174        if (configuration.isEnabled()) {
175          // It's enabled so always validate the class.
176          return isJavaClassAcceptable(configuration, unacceptableReasons);
177        } else {
178          // It's disabled so ignore it.
179          return true;
180        }
181      }
182    
183      /**
184       * {@inheritDoc}
185       */
186      public ConfigChangeResult applyConfigurationChange(
187           ExtendedOperationHandlerCfg configuration)
188      {
189        // Attempt to get the existing handler. This will only
190        // succeed if it was enabled.
191        DN dn = configuration.dn();
192        ExtendedOperationHandler handler = handlers.get(dn);
193    
194        // Default result code.
195        ResultCode resultCode = ResultCode.SUCCESS;
196        boolean adminActionRequired = false;
197        ArrayList<Message> messages = new ArrayList<Message>();
198    
199        // See whether the handler should be enabled.
200        if (handler == null) {
201          if (configuration.isEnabled()) {
202            // The handler needs to be enabled.
203            try {
204              handler = getHandler(configuration);
205    
206              // Put this handler in the hash so that we will
207              // be able to find it if it is altered.
208              handlers.put(dn, handler);
209    
210            } catch (ConfigException e) {
211              if (debugEnabled())
212              {
213                TRACER.debugCaught(DebugLogLevel.ERROR, e);
214              }
215    
216              messages.add(e.getMessageObject());
217              resultCode = DirectoryServer.getServerErrorResultCode();
218            } catch (Exception e) {
219              if (debugEnabled())
220              {
221                TRACER.debugCaught(DebugLogLevel.ERROR, e);
222              }
223    
224              messages.add(ERR_CONFIG_EXTOP_INITIALIZATION_FAILED.get(
225                      String.valueOf(configuration.getJavaClass()),
226                      String.valueOf(dn),
227                      stackTraceToSingleLineString(e)));
228              resultCode = DirectoryServer.getServerErrorResultCode();
229            }
230          }
231        } else {
232          if (configuration.isEnabled()) {
233            // The handler is currently active, so we don't
234            // need to do anything. Changes to the class name cannot be
235            // applied dynamically, so if the class name did change then
236            // indicate that administrative action is required for that
237            // change to take effect.
238            String className = configuration.getJavaClass();
239            if (!className.equals(handler.getClass().getName())) {
240              adminActionRequired = true;
241            }
242          } else {
243            // We need to disable the connection handler.
244    
245            handlers.remove(dn);
246    
247            handler.finalizeExtendedOperationHandler();
248          }
249        }
250    
251        // Return the configuration result.
252        return new ConfigChangeResult(resultCode, adminActionRequired,
253            messages);
254      }
255    
256      /**
257       * {@inheritDoc}
258       */
259      public boolean isConfigurationAddAcceptable(
260           ExtendedOperationHandlerCfg configuration,
261           List<Message> unacceptableReasons)
262      {
263        return isConfigurationChangeAcceptable(configuration, unacceptableReasons);
264      }
265    
266      /**
267       * {@inheritDoc}
268       */
269      public ConfigChangeResult applyConfigurationAdd(
270           ExtendedOperationHandlerCfg configuration)
271      {
272        // Default result code.
273        ResultCode resultCode = ResultCode.SUCCESS;
274        boolean adminActionRequired = false;
275        ArrayList<Message> messages = new ArrayList<Message>();
276    
277        // Register as a change listener for this connection handler entry
278        // so that we will be notified of any changes that may be made to
279        // it.
280        configuration.addChangeListener(this);
281    
282        // Ignore this connection handler if it is disabled.
283        if (configuration.isEnabled())
284        {
285          // The connection handler needs to be enabled.
286          DN dn = configuration.dn();
287          try {
288            ExtendedOperationHandler handler = getHandler(configuration);
289    
290            // Put this connection handler in the hash so that we will be
291            // able to find it if it is altered.
292            handlers.put(dn, handler);
293    
294          }
295          catch (ConfigException e)
296          {
297            if (debugEnabled())
298            {
299              TRACER.debugCaught(DebugLogLevel.ERROR, e);
300            }
301    
302            messages.add(e.getMessageObject());
303            resultCode = DirectoryServer.getServerErrorResultCode();
304          }
305          catch (Exception e)
306          {
307            if (debugEnabled())
308            {
309              TRACER.debugCaught(DebugLogLevel.ERROR, e);
310            }
311    
312            messages.add(ERR_CONFIG_EXTOP_INITIALIZATION_FAILED.get(
313                    String.valueOf(configuration.getJavaClass()),
314                    String.valueOf(dn),
315                    stackTraceToSingleLineString(e)));
316            resultCode = DirectoryServer.getServerErrorResultCode();
317          }
318        }
319    
320        // Return the configuration result.
321        return new ConfigChangeResult(resultCode, adminActionRequired,
322            messages);
323      }
324    
325      /**
326       * {@inheritDoc}
327       */
328      public boolean isConfigurationDeleteAcceptable(
329           ExtendedOperationHandlerCfg configuration,
330           List<Message> unacceptableReasons)
331      {
332        // A delete should always be acceptable, so just return true.
333        return true;
334      }
335    
336      // Load and initialize the handler named in the config.
337      private ExtendedOperationHandler getHandler(
338          ExtendedOperationHandlerCfg config) throws ConfigException
339      {
340        String className = config.getJavaClass();
341        ExtendedOperationHandlerCfgDefn d =
342          ExtendedOperationHandlerCfgDefn.getInstance();
343        ClassPropertyDefinition pd = d
344            .getJavaClassPropertyDefinition();
345    
346        // Load the class and cast it to an extended operation handler.
347        Class<? extends ExtendedOperationHandler> theClass;
348        ExtendedOperationHandler extendedOperationHandler;
349    
350        try
351        {
352          theClass = pd.loadClass(className, ExtendedOperationHandler.class);
353          extendedOperationHandler = theClass.newInstance();
354    
355          // Determine the initialization method to use: it must take a
356          // single parameter which is the exact type of the configuration
357          // object.
358          Method method = theClass.getMethod("initializeExtendedOperationHandler",
359              config.configurationClass());
360    
361          method.invoke(extendedOperationHandler, config);
362        }
363        catch (Exception e)
364        {
365          if (debugEnabled())
366          {
367            TRACER.debugCaught(DebugLogLevel.ERROR, e);
368          }
369    
370          Message message = ERR_CONFIG_EXTOP_INVALID_CLASS.
371              get(String.valueOf(className), String.valueOf(config.dn()),
372                  String.valueOf(e));
373          throw new ConfigException(message, e);
374        }
375    
376        // The handler has been successfully initialized.
377        return extendedOperationHandler;
378      }
379    
380    
381    
382      // Determines whether or not the new configuration's implementation
383      // class is acceptable.
384      private boolean isJavaClassAcceptable(ExtendedOperationHandlerCfg config,
385                                            List<Message> unacceptableReasons)
386      {
387        String className = config.getJavaClass();
388        ExtendedOperationHandlerCfgDefn d =
389          ExtendedOperationHandlerCfgDefn.getInstance();
390        ClassPropertyDefinition pd = d
391            .getJavaClassPropertyDefinition();
392    
393        // Load the class and cast it to an extended operation handler.
394        Class<? extends ExtendedOperationHandler> theClass;
395        try {
396          theClass = pd.loadClass(className, ExtendedOperationHandler.class);
397          ExtendedOperationHandler extOpHandler = theClass.newInstance();
398    
399          // Determine the initialization method to use: it must take a
400          // single parameter which is the exact type of the configuration
401          // object.
402          Method method = theClass.getMethod("isConfigurationAcceptable",
403                                             ExtendedOperationHandlerCfg.class,
404                                             List.class);
405          Boolean acceptable = (Boolean) method.invoke(extOpHandler, config,
406                                                       unacceptableReasons);
407    
408          if (! acceptable)
409          {
410            return false;
411          }
412        }
413        catch (Exception e)
414        {
415          if (debugEnabled())
416          {
417            TRACER.debugCaught(DebugLogLevel.ERROR, e);
418          }
419    
420          unacceptableReasons.add(ERR_CONFIG_EXTOP_INVALID_CLASS.get(className,
421                                  String.valueOf(config.dn()),
422                                  String.valueOf(e)));
423          return false;
424        }
425    
426        // The class is valid as far as we can tell.
427        return true;
428      }
429    }
430