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