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.lang.reflect.Method;
033    import java.util.ArrayList;
034    import java.util.Iterator;
035    import java.util.List;
036    import java.util.concurrent.ConcurrentHashMap;
037    
038    import org.opends.server.admin.ClassPropertyDefinition;
039    import org.opends.server.admin.server.ConfigurationAddListener;
040    import org.opends.server.admin.server.ConfigurationChangeListener;
041    import org.opends.server.admin.server.ConfigurationDeleteListener;
042    import org.opends.server.admin.server.ServerManagementContext;
043    import org.opends.server.admin.std.meta.PasswordGeneratorCfgDefn;
044    import org.opends.server.admin.std.server.PasswordGeneratorCfg;
045    import org.opends.server.admin.std.server.RootCfg;
046    import org.opends.server.api.PasswordGenerator;
047    import org.opends.server.config.ConfigException;
048    import org.opends.server.types.ConfigChangeResult;
049    import org.opends.server.types.DN;
050    import org.opends.server.types.InitializationException;
051    import org.opends.server.types.ResultCode;
052    
053    import static org.opends.messages.ConfigMessages.*;
054    
055    import static org.opends.server.loggers.ErrorLogger.*;
056    import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
057    
058    
059    
060    /**
061     * This class defines a utility that will be used to manage the set of password
062     * generators defined in the Directory Server.  It will initialize the
063     * generators when the server starts, and then will manage any additions,
064     * removals, or modifications to any password generators while the server is
065     * running.
066     */
067    public class PasswordGeneratorConfigManager
068           implements ConfigurationAddListener<PasswordGeneratorCfg>,
069           ConfigurationDeleteListener<PasswordGeneratorCfg>,
070           ConfigurationChangeListener<PasswordGeneratorCfg>
071    {
072    
073      // A mapping between the DNs of the config entries and the associated password
074      // generators.
075      private ConcurrentHashMap<DN,PasswordGenerator> passwordGenerators;
076    
077    
078      /**
079       * Creates a new instance of this password generator config manager.
080       */
081      public PasswordGeneratorConfigManager()
082      {
083        passwordGenerators = new ConcurrentHashMap<DN,PasswordGenerator>();
084      }
085    
086    
087    
088      /**
089       * Initializes all password generators currently defined in the Directory
090       * Server configuration.  This should only be called at Directory Server
091       * startup.
092       *
093       * @throws  ConfigException  If a configuration problem causes the password
094       *                           generator initialization process to fail.
095       *
096       * @throws  InitializationException  If a problem occurs while initializing
097       *                                   the password generators that is not
098       *                                   related to the server configuration.
099       */
100      public void initializePasswordGenerators()
101             throws ConfigException, InitializationException
102      {
103        // Get the root configuration object.
104        ServerManagementContext managementContext =
105             ServerManagementContext.getInstance();
106        RootCfg rootConfiguration =
107             managementContext.getRootConfiguration();
108    
109        // Register as an add and delete listener with the root configuration so we
110        // can be notified if any password generator entries are added or removed.
111        rootConfiguration.addPasswordGeneratorAddListener(this);
112        rootConfiguration.addPasswordGeneratorDeleteListener(this);
113    
114    
115        //Initialize the existing password generators.
116        for (String generatorName : rootConfiguration.listPasswordGenerators())
117        {
118          PasswordGeneratorCfg generatorConfiguration =
119               rootConfiguration.getPasswordGenerator(generatorName);
120          generatorConfiguration.addChangeListener(this);
121    
122          if (generatorConfiguration.isEnabled())
123          {
124            String className = generatorConfiguration.getJavaClass();
125            try
126            {
127              PasswordGenerator<? extends PasswordGeneratorCfg>
128                   generator = loadGenerator(className, generatorConfiguration,
129                                             true);
130              passwordGenerators.put(generatorConfiguration.dn(), generator);
131              DirectoryServer.registerPasswordGenerator(generatorConfiguration.dn(),
132                  generator);
133            }
134            catch (InitializationException ie)
135            {
136              logError(ie.getMessageObject());
137              continue;
138            }
139          }
140        }
141      }
142    
143      /**
144       * {@inheritDoc}
145       */
146      public boolean isConfigurationChangeAcceptable(
147                          PasswordGeneratorCfg configuration,
148                          List<Message> unacceptableReasons)
149      {
150        if (configuration.isEnabled())
151        {
152          // Get the name of the class and make sure we can instantiate it as a
153          // password generator.
154          String className = configuration.getJavaClass();
155          try
156          {
157            loadGenerator(className, configuration, false);
158          }
159          catch (InitializationException ie)
160          {
161            unacceptableReasons.add(ie.getMessageObject());
162            return false;
163          }
164        }
165    
166        // If we've gotten here, then it's fine.
167        return true;
168      }
169    
170    
171      /**
172       * {@inheritDoc}
173       */
174      public ConfigChangeResult applyConfigurationChange(
175                                     PasswordGeneratorCfg configuration)
176      {
177        ResultCode        resultCode          = ResultCode.SUCCESS;
178        boolean           adminActionRequired = false;
179        ArrayList<Message> messages            = new ArrayList<Message>();
180    
181    
182        // Get the existing generator if it's already enabled.
183        PasswordGenerator existingGenerator =
184             passwordGenerators.get(configuration.dn());
185    
186    
187        // If the new configuration has the generator disabled, then disable it if
188        // it is enabled, or do nothing if it's already disabled.
189        if (! configuration.isEnabled())
190        {
191          if (existingGenerator != null)
192          {
193            DirectoryServer.deregisterPasswordGenerator(configuration.dn());
194    
195            PasswordGenerator passwordGenerator =
196                 passwordGenerators.remove(configuration.dn());
197            if (passwordGenerator != null)
198            {
199              passwordGenerator.finalizePasswordGenerator();
200            }
201          }
202    
203          return new ConfigChangeResult(resultCode, adminActionRequired, messages);
204        }
205    
206    
207        // Get the class for the password generator.  If the generator is already
208        // enabled, then we shouldn't do anything with it although if the class has
209        // changed then we'll at least need to indicate that administrative action
210        // is required.  If the generator is disabled, then instantiate the class
211        // and initialize and register it as a password generator.
212        String className = configuration.getJavaClass();
213        if (existingGenerator != null)
214        {
215          if (! className.equals(existingGenerator.getClass().getName()))
216          {
217            adminActionRequired = true;
218          }
219    
220          return new ConfigChangeResult(resultCode, adminActionRequired, messages);
221        }
222    
223        PasswordGenerator<? extends PasswordGeneratorCfg>
224             passwordGenerator = null;
225        try
226        {
227          passwordGenerator = loadGenerator(className, configuration, true);
228        }
229        catch (InitializationException ie)
230        {
231          if (resultCode == ResultCode.SUCCESS)
232          {
233            resultCode = DirectoryServer.getServerErrorResultCode();
234          }
235    
236          messages.add(ie.getMessageObject());
237        }
238    
239        if (resultCode == ResultCode.SUCCESS)
240        {
241          passwordGenerators.put(configuration.dn(), passwordGenerator);
242          DirectoryServer.registerPasswordGenerator(configuration.dn(),
243                                                    passwordGenerator);
244        }
245    
246        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
247      }
248      /**
249       * {@inheritDoc}
250       */
251      public boolean isConfigurationAddAcceptable(
252                          PasswordGeneratorCfg configuration,
253                          List<Message> unacceptableReasons)
254      {
255        if (configuration.isEnabled())
256        {
257          // Get the name of the class and make sure we can instantiate it as a
258          // password generator.
259          String className = configuration.getJavaClass();
260          try
261          {
262            loadGenerator(className, configuration, false);
263          }
264          catch (InitializationException ie)
265          {
266            unacceptableReasons.add(ie.getMessageObject());
267            return false;
268          }
269        }
270    
271        // If we've gotten here, then it's fine.
272        return true;
273      }
274    
275    
276      /**
277       * {@inheritDoc}
278       */
279      public ConfigChangeResult applyConfigurationAdd(
280                                     PasswordGeneratorCfg configuration)
281      {
282        ResultCode        resultCode          = ResultCode.SUCCESS;
283        boolean           adminActionRequired = false;
284        ArrayList<Message> messages            = new ArrayList<Message>();
285    
286        configuration.addChangeListener(this);
287    
288        if (! configuration.isEnabled())
289        {
290          return new ConfigChangeResult(resultCode, adminActionRequired, messages);
291        }
292    
293        PasswordGenerator<? extends PasswordGeneratorCfg>
294             passwordGenerator = null;
295    
296        // Get the name of the class and make sure we can instantiate it as a
297        // password generator.
298        String className = configuration.getJavaClass();
299        try
300        {
301          passwordGenerator = loadGenerator(className, configuration, true);
302        }
303        catch (InitializationException ie)
304        {
305          if (resultCode == ResultCode.SUCCESS)
306          {
307            resultCode = DirectoryServer.getServerErrorResultCode();
308          }
309    
310          messages.add(ie.getMessageObject());
311        }
312    
313        if (resultCode == ResultCode.SUCCESS)
314        {
315          passwordGenerators.put(configuration.dn(), passwordGenerator);
316          DirectoryServer.registerPasswordGenerator(configuration.dn(),
317                                                    passwordGenerator);
318        }
319    
320        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
321      }
322    
323      /**
324       * {@inheritDoc}
325       */
326      public boolean isConfigurationDeleteAcceptable(
327          PasswordGeneratorCfg configuration, List<Message> unacceptableReasons)
328      {
329        // A delete should always be acceptable, so just return true.
330        return true;
331      }
332    
333    
334      /**
335       * {@inheritDoc}
336       */
337      public ConfigChangeResult applyConfigurationDelete(
338          PasswordGeneratorCfg configuration)
339      {
340        ResultCode resultCode          = ResultCode.SUCCESS;
341        boolean    adminActionRequired = false;
342    
343    
344        // See if the entry is registered as a password generator.  If so,
345        // deregister it and stop the generator.
346        PasswordGenerator generator = passwordGenerators.remove(configuration.dn());
347        if (generator != null)
348        {
349          DirectoryServer.deregisterPasswordGenerator(configuration.dn());
350    
351          generator.finalizePasswordGenerator();
352        }
353    
354    
355        return new ConfigChangeResult(resultCode, adminActionRequired);
356      }
357    
358      /**
359       * Loads the specified class, instantiates it as a password generator, and
360       * optionally initializes that instance.
361       *
362       * @param  className      The fully-qualified name of the password generator
363       *                        class to load, instantiate, and initialize.
364       * @param  configuration  The configuration to use to initialize the
365       *                        password generator, or {@code null} if the
366       *                        password generator should not be initialized.
367       * @param  initialize     Indicates whether the password generator instance
368       *                        should be initialized.
369       *
370       * @return  The possibly initialized password generator.
371       *
372       * @throws  InitializationException  If a problem occurred while attempting to
373       *                                   initialize the password generator.
374       */
375      private PasswordGenerator<? extends PasswordGeneratorCfg>
376                   loadGenerator(String className,
377                                 PasswordGeneratorCfg configuration,
378                                 boolean initialize)
379              throws InitializationException
380      {
381        try
382        {
383          PasswordGeneratorCfgDefn definition =
384               PasswordGeneratorCfgDefn.getInstance();
385          ClassPropertyDefinition propertyDefinition =
386               definition.getJavaClassPropertyDefinition();
387          Class<? extends PasswordGenerator> generatorClass =
388               propertyDefinition.loadClass(className, PasswordGenerator.class);
389          PasswordGenerator<? extends PasswordGeneratorCfg> generator =
390               (PasswordGenerator<? extends PasswordGeneratorCfg>)
391               generatorClass.newInstance();
392    
393          if (initialize)
394          {
395            Method method = generator.getClass().getMethod(
396                "initializePasswordGenerator", configuration.configurationClass());
397            method.invoke(generator, configuration);
398          }
399          else
400          {
401            Method method =
402                 generator.getClass().getMethod("isConfigurationAcceptable",
403                                                PasswordGeneratorCfg.class,
404                                                List.class);
405    
406            List<Message> unacceptableReasons = new ArrayList<Message>();
407            Boolean acceptable = (Boolean) method.invoke(generator, configuration,
408                                                         unacceptableReasons);
409            if (! acceptable)
410            {
411              StringBuilder buffer = new StringBuilder();
412              if (! unacceptableReasons.isEmpty())
413              {
414                Iterator<Message> iterator = unacceptableReasons.iterator();
415                buffer.append(iterator.next());
416                while (iterator.hasNext())
417                {
418                  buffer.append(".  ");
419                  buffer.append(iterator.next());
420                }
421              }
422    
423              Message message = ERR_CONFIG_PWGENERATOR_CONFIG_NOT_ACCEPTABLE.get(
424                  String.valueOf(configuration.dn()), buffer.toString());
425              throw new InitializationException(message);
426            }
427          }
428    
429          return generator;
430        }
431        catch (Exception e)
432        {
433          Message message = ERR_CONFIG_PWGENERATOR_INITIALIZATION_FAILED.
434              get(className, String.valueOf(configuration.dn()),
435                  stackTraceToSingleLineString(e));
436          throw new InitializationException(message, e);
437        }
438      }
439    }
440