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 static org.opends.server.loggers.debug.DebugLogger.*;
033 import org.opends.server.loggers.debug.DebugTracer;
034 import static org.opends.messages.ConfigMessages.*;
035
036 import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
037
038 import java.lang.reflect.Method;
039 import java.util.ArrayList;
040 import java.util.List;
041 import java.util.concurrent.ConcurrentHashMap;
042
043 import org.opends.server.admin.ClassPropertyDefinition;
044 import org.opends.server.admin.server.ConfigurationAddListener;
045 import org.opends.server.admin.server.ConfigurationChangeListener;
046 import org.opends.server.admin.server.ConfigurationDeleteListener;
047 import org.opends.server.admin.server.ServerManagementContext;
048 import org.opends.server.admin.std.meta.SynchronizationProviderCfgDefn;
049 import org.opends.server.admin.std.server.RootCfg;
050 import org.opends.server.admin.std.server.SynchronizationProviderCfg;
051 import org.opends.server.api.SynchronizationProvider;
052 import org.opends.server.config.ConfigException;
053 import org.opends.server.types.ConfigChangeResult;
054 import org.opends.server.types.DN;
055 import org.opends.server.types.DebugLogLevel;
056 import org.opends.server.types.InitializationException;
057 import org.opends.server.types.ResultCode;
058
059
060
061 /**
062 * This class defines a utility that will be used to manage the configuration
063 * for the set of synchronization providers configured in the Directory Server.
064 * It will perform the necessary initialization of those synchronization
065 * providers when the server is first started, and then will manage any changes
066 * to them while the server is running.
067 */
068 public class SynchronizationProviderConfigManager
069 implements ConfigurationChangeListener<SynchronizationProviderCfg>,
070 ConfigurationAddListener<SynchronizationProviderCfg>,
071 ConfigurationDeleteListener<SynchronizationProviderCfg>
072 {
073 /**
074 * The tracer object for the debug logger.
075 */
076 private static final DebugTracer TRACER = getTracer();
077
078
079
080
081 // The mapping between configuration entry DNs and their corresponding
082 // synchronization provider implementations.
083 private ConcurrentHashMap<DN,
084 SynchronizationProvider<SynchronizationProviderCfg>> registeredProviders =
085 new ConcurrentHashMap<DN,
086 SynchronizationProvider<SynchronizationProviderCfg>>();
087
088
089
090
091 /**
092 * Creates a new instance of this synchronization provider config manager.
093 */
094 public SynchronizationProviderConfigManager()
095 {
096 // No implementation is required.
097 }
098
099
100
101 /**
102 * Initializes the configuration associated with the Directory Server
103 * synchronization providers. This should only be called at Directory Server
104 * startup.
105 *
106 * @throws ConfigException If a critical configuration problem prevents any
107 * of the synchronization providers from starting
108 * properly.
109 *
110 * @throws InitializationException If a problem occurs while initializing
111 * any of the synchronization providers that
112 * is not related to the Directory Server
113 * configuration.
114 */
115 public void initializeSynchronizationProviders()
116 throws ConfigException, InitializationException
117 {
118 // Create an internal server management context and retrieve
119 // the root configuration which has the synchronization provider relation.
120 ServerManagementContext context = ServerManagementContext.getInstance();
121 RootCfg root = context.getRootConfiguration();
122
123 // Register as an add and delete listener so that we can
124 // be notified when new synchronization providers are added or existing
125 // sycnhronization providers are removed.
126 root.addSynchronizationProviderAddListener(this);
127 root.addSynchronizationProviderDeleteListener(this);
128
129 // Initialize existing synchronization providers.
130 for (String name : root.listSynchronizationProviders())
131 {
132 // Get the synchronization provider's configuration.
133 // This will automatically decode and validate its properties.
134 SynchronizationProviderCfg config = root.getSynchronizationProvider(name);
135
136 // Register as a change listener for this synchronization provider
137 // entry so that we can be notified when it is disabled or enabled.
138 config.addChangeListener(this);
139
140 // Ignore this synchronization provider if it is disabled.
141 if (config.isEnabled())
142 {
143 // Perform initialization, load the synchronization provider's
144 // implementation class and initialize it.
145 SynchronizationProvider<SynchronizationProviderCfg> provider =
146 getSynchronizationProvider(config);
147
148 // Register the synchronization provider with the Directory Server.
149 DirectoryServer.registerSynchronizationProvider(provider);
150
151 // Put this synchronization provider in the hash map so that we will be
152 // able to find it if it is deleted or disabled.
153 registeredProviders.put(config.dn(), provider);
154 }
155 }
156 }
157
158
159
160 /**
161 * {@inheritDoc}
162 */
163 public ConfigChangeResult applyConfigurationChange(
164 SynchronizationProviderCfg configuration)
165 {
166 // Default result code.
167 ResultCode resultCode = ResultCode.SUCCESS;
168 boolean adminActionRequired = false;
169 ArrayList<Message> messages = new ArrayList<Message>();
170
171 // Attempt to get the existing synchronization provider. This will only
172 // succeed if it is currently enabled.
173 DN dn = configuration.dn();
174 SynchronizationProvider<SynchronizationProviderCfg> provider =
175 registeredProviders.get(dn);
176
177 // See whether the synchronization provider should be enabled.
178 if (provider == null)
179 {
180 if (configuration.isEnabled())
181 {
182 // The synchronization provider needs to be enabled. Load, initialize,
183 // and register the synchronization provider as per the add listener
184 // method.
185 try
186 {
187 // Perform initialization, load the synchronization provider's
188 // implementation class and initialize it.
189 provider = getSynchronizationProvider(configuration);
190
191 // Register the synchronization provider with the Directory Server.
192 DirectoryServer.registerSynchronizationProvider(provider);
193
194 // Put this synchronization provider in the hash map so that we will
195 // be able to find it if it is deleted or disabled.
196 registeredProviders.put(configuration.dn(), provider);
197 }
198 catch (ConfigException e)
199 {
200 if (debugEnabled())
201 {
202 TRACER.debugCaught(DebugLogLevel.ERROR, e);
203 messages.add(e.getMessageObject());
204 resultCode = DirectoryServer.getServerErrorResultCode();
205 }
206 }
207 catch (Exception e)
208 {
209 if (debugEnabled())
210 {
211 TRACER.debugCaught(DebugLogLevel.ERROR, e);
212 }
213
214 messages.add(ERR_CONFIG_SYNCH_ERROR_INITIALIZING_PROVIDER.get(
215 String.valueOf(configuration.getJavaClass()),
216 String.valueOf(configuration.dn())));
217 resultCode = DirectoryServer.getServerErrorResultCode();
218 }
219 }
220 }
221 else
222 {
223 if (configuration.isEnabled())
224 {
225 // The synchronization provider is currently active, so we don't
226 // need to do anything. Changes to the class name cannot be
227 // applied dynamically, so if the class name did change then
228 // indicate that administrative action is required for that
229 // change to take effect.
230 String className = configuration.getJavaClass();
231 if (!className.equals(provider.getClass().getName()))
232 {
233 adminActionRequired = true;
234 }
235 }
236 else
237 {
238 // The connection handler is being disabled so remove it from
239 // the DirectorySerevr list, shut it down and remove it from the
240 // hash map.
241 DirectoryServer.deregisterSynchronizationProvider(provider);
242 provider.finalizeSynchronizationProvider();
243 registeredProviders.remove(dn);
244 }
245 }
246 // Return the configuration result.
247 return new ConfigChangeResult(resultCode, adminActionRequired,
248 messages);
249 }
250
251
252
253 /**
254 * {@inheritDoc}
255 */
256 public boolean isConfigurationChangeAcceptable(
257 SynchronizationProviderCfg configuration,
258 List<Message> unacceptableReasons)
259 {
260 if (configuration.isEnabled())
261 {
262 // It's enabled so always validate the class.
263 return isJavaClassAcceptable(configuration, unacceptableReasons);
264 } else
265 {
266 // It's disabled so ignore it.
267 return true;
268 }
269 }
270
271
272
273 /**
274 * {@inheritDoc}
275 */
276 public ConfigChangeResult applyConfigurationAdd(
277 SynchronizationProviderCfg configuration)
278 {
279 // Default result code.
280 ResultCode resultCode = ResultCode.SUCCESS;
281 boolean adminActionRequired = false;
282 ArrayList<Message> messages = new ArrayList<Message>();
283
284 // Register as a change listener for this synchronization provider entry
285 // so that we will be notified if when it is disabled or enabled.
286 configuration.addChangeListener(this);
287
288 // Ignore this synchronization provider if it is disabled.
289 if (configuration.isEnabled())
290 {
291 try
292 {
293 // Perform initialization, load the synchronization provider's
294 // implementation class and initialize it.
295 SynchronizationProvider<SynchronizationProviderCfg> provider =
296 getSynchronizationProvider(configuration);
297
298 // Register the synchronization provider with the Directory Server.
299 DirectoryServer.registerSynchronizationProvider(provider);
300
301 // Put this synchronization provider in the hash map so that we will be
302 // able to find it if it is deleted or disabled.
303 registeredProviders.put(configuration.dn(), provider);
304 }
305 catch (ConfigException e)
306 {
307 if (debugEnabled())
308 {
309 TRACER.debugCaught(DebugLogLevel.ERROR, e);
310 messages.add(e.getMessageObject());
311 resultCode = DirectoryServer.getServerErrorResultCode();
312 }
313 }
314 catch (Exception e)
315 {
316 if (debugEnabled())
317 {
318 TRACER.debugCaught(DebugLogLevel.ERROR, e);
319 }
320
321 messages.add(ERR_CONFIG_SYNCH_ERROR_INITIALIZING_PROVIDER.get(
322 String.valueOf(configuration.getJavaClass()),
323 String.valueOf(configuration.dn())));
324 resultCode = DirectoryServer.getServerErrorResultCode();
325 }
326 }
327
328 // Return the configuration result.
329 return new ConfigChangeResult(resultCode, adminActionRequired,
330 messages);
331 }
332
333
334
335 /**
336 * {@inheritDoc}
337 */
338 public boolean isConfigurationAddAcceptable(
339 SynchronizationProviderCfg configuration,
340 List<Message> unacceptableReasons)
341 {
342 if (configuration.isEnabled())
343 {
344 // It's enabled so always validate the class.
345 return isJavaClassAcceptable(configuration, unacceptableReasons);
346 } else
347 {
348 // It's disabled so ignore it.
349 return true;
350 }
351 }
352
353
354
355 /**
356 * Check if the class provided in the configuration is an acceptable
357 * java class for a synchronization provider.
358 *
359 * @param configuration The configuration for which the class must be
360 * checked.
361 * @return true if the class is acceptable or false if not.
362 */
363 @SuppressWarnings("unchecked")
364 private SynchronizationProvider<SynchronizationProviderCfg>
365 getSynchronizationProvider(SynchronizationProviderCfg configuration)
366 throws ConfigException
367 {
368 String className = configuration.getJavaClass();
369 SynchronizationProviderCfgDefn d =
370 SynchronizationProviderCfgDefn.getInstance();
371 ClassPropertyDefinition pd =
372 d.getJavaClassPropertyDefinition();
373
374 // Load the class
375 Class<? extends SynchronizationProvider> theClass;
376 SynchronizationProvider<SynchronizationProviderCfg> provider;
377 try
378 {
379 theClass = pd.loadClass(className, SynchronizationProvider.class);
380 } catch (Exception e)
381 {
382 // Handle the exception: put a message in the unacceptable reasons.
383 Message message = ERR_CONFIG_SYNCH_UNABLE_TO_LOAD_PROVIDER_CLASS.
384 get(String.valueOf(className), String.valueOf(configuration.dn()),
385 stackTraceToSingleLineString(e));
386 throw new ConfigException(message, e);
387 }
388 try
389 {
390 // Instantiate the class.
391 provider = theClass.newInstance();
392 } catch (Exception e)
393 {
394 // Handle the exception: put a message in the unacceptable reasons.
395 Message message = ERR_CONFIG_SYNCH_UNABLE_TO_INSTANTIATE_PROVIDER.
396 get(String.valueOf(className), String.valueOf(configuration.dn()),
397 stackTraceToSingleLineString(e));
398 throw new ConfigException(message, e);
399 }
400 try
401 {
402 // Initialize the Synchronization Provider.
403 provider.initializeSynchronizationProvider(configuration);
404 } catch (Exception e)
405 {
406 // Handle the exception: put a message in the unacceptable reasons.
407 Message message = ERR_CONFIG_SYNCH_ERROR_INITIALIZING_PROVIDER.get(
408 String.valueOf(className), String.valueOf(configuration.dn()));
409 throw new ConfigException(message, e);
410 }
411 return provider;
412 }
413
414 /**
415 * Check if the class provided in the configuration is an acceptable
416 * java class for a synchronization provider.
417 *
418 * @param configuration The configuration for which the class must be
419 * checked.
420 * @param unacceptableReasons A list containing the reasons why the class is
421 * not acceptable.
422 *
423 * @return true if the class is acceptable or false if not.
424 */
425 private boolean isJavaClassAcceptable(
426 SynchronizationProviderCfg configuration,
427 List<Message> unacceptableReasons)
428 {
429 String className = configuration.getJavaClass();
430 SynchronizationProviderCfgDefn d =
431 SynchronizationProviderCfgDefn.getInstance();
432 ClassPropertyDefinition pd =
433 d.getJavaClassPropertyDefinition();
434
435 // Load the class and cast it to a synchronizationProvider.
436 SynchronizationProvider provider = null;
437 Class<? extends SynchronizationProvider> theClass;
438 try
439 {
440 theClass = pd.loadClass(className, SynchronizationProvider.class);
441 provider = theClass.newInstance();
442 } catch (Exception e)
443 {
444 // Handle the exception: put a message in the unacceptable reasons.
445 Message message = ERR_CONFIG_SYNCH_UNABLE_TO_LOAD_PROVIDER_CLASS.get(
446 String.valueOf(className),
447 String.valueOf(configuration.dn()),
448 stackTraceToSingleLineString(e));
449 unacceptableReasons.add(message);
450 return false;
451 }
452 // Check that the implementation class implements the correct interface.
453 try
454 {
455 // Determine the initialization method to use: it must take a
456 // single parameter which is the exact type of the configuration
457 // object.
458 Method method = theClass.getMethod("isConfigurationAcceptable",
459 SynchronizationProviderCfg.class,
460 List.class);
461 Boolean acceptable = (Boolean) method.invoke(provider, configuration,
462 unacceptableReasons);
463
464 if (! acceptable)
465 {
466 return false;
467 }
468 } catch (Exception e)
469 {
470 // Handle the exception: put a message in the unacceptable reasons.
471 Message message = ERR_CONFIG_SYNCH_UNABLE_TO_INSTANTIATE_PROVIDER.get(
472 String.valueOf(className),
473 String.valueOf(configuration.dn()),
474 stackTraceToSingleLineString(e));
475 unacceptableReasons.add(message);
476 return false;
477 }
478
479 // The class is valid as far as we can tell.
480 return true;
481 }
482
483 /**
484 * {@inheritDoc}
485 */
486 public ConfigChangeResult applyConfigurationDelete(
487 SynchronizationProviderCfg configuration)
488 {
489 // Default result code.
490 ResultCode resultCode = ResultCode.SUCCESS;
491 boolean adminActionRequired = false;
492
493 // See if the entry is registered as a synchronization provider. If so,
494 // deregister and stop it.
495 DN dn = configuration.dn();
496 SynchronizationProvider provider = registeredProviders.get(dn);
497 if (provider != null)
498 {
499 DirectoryServer.deregisterSynchronizationProvider(provider);
500 provider.finalizeSynchronizationProvider();
501 }
502 return new ConfigChangeResult(resultCode, adminActionRequired);
503 }
504
505
506
507 /**
508 * {@inheritDoc}
509 */
510 public boolean isConfigurationDeleteAcceptable(
511 SynchronizationProviderCfg configuration,
512 List<Message> unacceptableReasons)
513 {
514 // A delete should always be acceptable, so just return true.
515 return true;
516 }
517 }
518
519
520
521