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