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.backends.jeb;
028    
029    import com.sleepycat.je.EnvironmentConfig;
030    
031    import org.opends.server.config.ConfigConstants;
032    import org.opends.server.config.ConfigException;
033    import org.opends.server.types.DebugLogLevel;
034    
035    import java.util.HashMap;
036    import java.util.Map;
037    import java.lang.reflect.Method;
038    import java.util.HashSet;
039    import java.util.SortedSet;
040    import java.util.StringTokenizer;
041    import java.util.List;
042    import java.util.Arrays;
043    
044    import org.opends.messages.Message;
045    
046    import org.opends.server.loggers.debug.DebugTracer;
047    import org.opends.server.admin.std.server.LocalDBBackendCfg;
048    import org.opends.server.admin.std.meta.LocalDBBackendCfgDefn;
049    import org.opends.server.admin.DurationPropertyDefinition;
050    import org.opends.server.admin.BooleanPropertyDefinition;
051    import org.opends.server.admin.PropertyDefinition;
052    
053    import static org.opends.server.loggers.debug.DebugLogger.*;
054    import static org.opends.messages.ConfigMessages.*;
055    
056    /**
057     * This class maps JE properties to configuration attributes.
058     */
059    public class ConfigurableEnvironment
060    {
061      /**
062       * The tracer object for the debug logger.
063       */
064      private static final DebugTracer TRACER = getTracer();
065    
066      /**
067       * The name of the attribute which configures the database cache size as a
068       * percentage of Java VM heap size.
069       */
070      public static final String ATTR_DATABASE_CACHE_PERCENT =
071           ConfigConstants.NAME_PREFIX_CFG + "db-cache-percent";
072    
073      /**
074       * The name of the attribute which configures the database cache size as an
075       * approximate number of bytes.
076       */
077      public static final String ATTR_DATABASE_CACHE_SIZE =
078           ConfigConstants.NAME_PREFIX_CFG + "db-cache-size";
079    
080      /**
081       * The name of the attribute which configures whether data updated by a
082       * database transaction is forced to disk.
083       */
084      public static final String ATTR_DATABASE_TXN_NO_SYNC =
085           ConfigConstants.NAME_PREFIX_CFG + "db-txn-no-sync";
086    
087      /**
088       * The name of the attribute which configures whether data updated by a
089       * database transaction is written from the Java VM to the O/S.
090       */
091      public static final String ATTR_DATABASE_TXN_WRITE_NO_SYNC =
092           ConfigConstants.NAME_PREFIX_CFG + "db-txn-write-no-sync";
093    
094      /**
095       * The name of the attribute which configures whether the database background
096       * cleaner thread runs.
097       */
098      public static final String ATTR_DATABASE_RUN_CLEANER =
099           ConfigConstants.NAME_PREFIX_CFG + "db-run-cleaner";
100    
101      /**
102       * The name of the attribute which configures the minimum percentage of log
103       * space that must be used in log files.
104       */
105      public static final String ATTR_CLEANER_MIN_UTILIZATION =
106           ConfigConstants.NAME_PREFIX_CFG + "db-cleaner-min-utilization";
107    
108      /**
109       * The name of the attribute which configures the maximum size of each
110       * individual JE log file, in bytes.
111       */
112      public static final String ATTR_DATABASE_LOG_FILE_MAX =
113           ConfigConstants.NAME_PREFIX_CFG + "db-log-file-max";
114    
115      /**
116       * The name of the attribute which configures the database cache eviction
117       * algorithm.
118       */
119      public static final String ATTR_EVICTOR_LRU_ONLY =
120           ConfigConstants.NAME_PREFIX_CFG + "db-evictor-lru-only";
121    
122      /**
123       * The name of the attribute which configures the number of nodes in one scan
124       * of the database cache evictor.
125       */
126      public static final String ATTR_EVICTOR_NODES_PER_SCAN =
127           ConfigConstants.NAME_PREFIX_CFG + "db-evictor-nodes-per-scan";
128    
129    
130      /**
131       * The name of the attribute which configures whether the logging file
132       * handler will be on or off.
133       */
134      public static final String ATTR_LOGGING_FILE_HANDLER_ON =
135           ConfigConstants.NAME_PREFIX_CFG + "db-logging-file-handler-on";
136    
137    
138      /**
139       * The name of the attribute which configures the trace logging message level.
140       */
141      public static final String ATTR_LOGGING_LEVEL =
142           ConfigConstants.NAME_PREFIX_CFG + "db-logging-level";
143    
144    
145      /**
146       * The name of the attribute which configures how many bytes are written to
147       * the log before the checkpointer runs.
148       */
149      public static final String ATTR_CHECKPOINTER_BYTES_INTERVAL =
150           ConfigConstants.NAME_PREFIX_CFG + "db-checkpointer-bytes-interval";
151    
152    
153      /**
154       * The name of the attribute which configures the amount of time between
155       * runs of the checkpointer.
156       */
157      public static final String ATTR_CHECKPOINTER_WAKEUP_INTERVAL =
158           ConfigConstants.NAME_PREFIX_CFG +
159           "db-checkpointer-wakeup-interval";
160    
161    
162      /**
163       * The name of the attribute which configures the number of lock tables.
164       */
165      public static final String ATTR_NUM_LOCK_TABLES =
166           ConfigConstants.NAME_PREFIX_CFG + "db-num-lock-tables";
167    
168    
169      /**
170       * The name of the attribute which configures the number threads
171       * allocated by the cleaner for log file processing.
172       */
173      public static final String ATTR_NUM_CLEANER_THREADS =
174           ConfigConstants.NAME_PREFIX_CFG + "db-num-cleaner-threads";
175    
176    
177      /**
178       * The name of the attribute which may specify any native JE properties.
179       */
180      public static final String ATTR_JE_PROPERTY =
181           ConfigConstants.NAME_PREFIX_CFG + "je-property";
182    
183    
184      /**
185       * A map of JE property names to the corresponding configuration attribute.
186       */
187      private static HashMap<String, String> attrMap =
188           new HashMap<String, String>();
189    
190      /**
191       * A map of configuration attribute names to the corresponding configuration
192       * object getter method.
193       */
194      private static HashMap<String,Method> methodMap =
195           new HashMap<String, Method>();
196    
197      /**
198       * A map of configuration attribute names to the corresponding configuration
199       * PropertyDefinition.
200       */
201      private static HashMap<String,PropertyDefinition> defnMap =
202           new HashMap<String, PropertyDefinition>();
203    
204    
205      // Pulled from resource/admin/ABBREVIATIONS.xsl.  db is mose common.
206      private static final List<String> ABBREVIATIONS = Arrays.asList(new String[]
207              {"aci", "ip", "ssl", "dn", "rdn", "jmx", "smtp", "http",
208               "https", "ldap", "ldaps", "ldif", "jdbc", "tcp", "tls",
209               "pkcs11", "sasl", "gssapi", "md5", "je", "dse", "fifo",
210               "vlv", "uuid", "md5", "sha1", "sha256", "sha384", "sha512",
211               "tls", "db"});
212    
213      /*
214       * e.g. db-cache-percent -> DBCachePercent
215       */
216      private static String propNametoCamlCase(String hyphenated)
217      {
218        String[] components = hyphenated.split("\\-");
219        StringBuilder buffer = new StringBuilder();
220        for (String component: components) {
221          if (ABBREVIATIONS.contains(component)) {
222            buffer.append(component.toUpperCase());
223          } else {
224            buffer.append(component.substring(0, 1).toUpperCase() +
225                    component.substring(1));
226          }
227        }
228        return buffer.toString();
229      }
230    
231    
232      /**
233       * Register a JE property and its corresponding configuration attribute.
234       *
235       * @param propertyName The name of the JE property to be registered.
236       * @param attrName     The name of the configuration attribute associated
237       *                     with the property.
238       * @throws Exception   If there is an error in the attribute name.
239       */
240      private static void registerProp(String propertyName, String attrName)
241           throws Exception
242      {
243        // Strip off NAME_PREFIX_CFG.
244        String baseName = attrName.substring(7);
245    
246        String methodBaseName = propNametoCamlCase(baseName);
247    
248        Class<LocalDBBackendCfg> configClass = LocalDBBackendCfg.class;
249        LocalDBBackendCfgDefn defn = LocalDBBackendCfgDefn.getInstance();
250        Class<? extends LocalDBBackendCfgDefn> defClass = defn.getClass();
251    
252        PropertyDefinition propDefn =
253             (PropertyDefinition)defClass.getMethod("get" + methodBaseName +
254             "PropertyDefinition").invoke(defn);
255    
256        String methodName;
257        if (propDefn instanceof BooleanPropertyDefinition)
258        {
259          methodName = "is" + methodBaseName;
260        }
261        else
262        {
263          methodName = "get" + methodBaseName;
264        }
265    
266        defnMap.put(attrName, propDefn);
267        methodMap.put(attrName, configClass.getMethod(methodName));
268        attrMap.put(propertyName, attrName);
269      }
270    
271    
272      /**
273       * Get the name of the configuration attribute associated with a JE property.
274       * @param jeProperty The name of the JE property.
275       * @return The name of the associated configuration attribute.
276       */
277      public static String getAttributeForProperty(String jeProperty)
278      {
279        return attrMap.get(jeProperty);
280      }
281    
282      /**
283       * Get the value of a JE property that is mapped to a configuration attribute.
284       * @param cfg The configuration containing the property values.
285       * @param attrName The conriguration attribute type name.
286       * @return The string value of the JE property.
287       */
288      private static String getPropertyValue(LocalDBBackendCfg cfg, String attrName)
289      {
290        try
291        {
292          PropertyDefinition propDefn = defnMap.get(attrName);
293          Method method = methodMap.get(attrName);
294    
295          if (propDefn instanceof DurationPropertyDefinition)
296          {
297            Long value = (Long)method.invoke(cfg);
298    
299            // JE durations are in microseconds so we must convert.
300            DurationPropertyDefinition durationPropDefn =
301                 (DurationPropertyDefinition)propDefn;
302            value = 1000*durationPropDefn.getBaseUnit().toMilliSeconds(value);
303    
304            return String.valueOf(value);
305          }
306          else
307          {
308            Object value = method.invoke(cfg);
309            return String.valueOf(value);
310          }
311        }
312        catch (Exception e)
313        {
314          if (debugEnabled())
315          {
316            TRACER.debugCaught(DebugLogLevel.ERROR, e);
317          }
318          return "";
319        }
320      }
321    
322    
323    
324      static
325      {
326        // Register the parameters that have JE property names.
327        try
328        {
329          registerProp("je.maxMemoryPercent", ATTR_DATABASE_CACHE_PERCENT);
330          registerProp("je.maxMemory", ATTR_DATABASE_CACHE_SIZE);
331          registerProp("je.cleaner.minUtilization", ATTR_CLEANER_MIN_UTILIZATION);
332          registerProp("je.env.runCleaner", ATTR_DATABASE_RUN_CLEANER);
333          registerProp("je.evictor.lruOnly", ATTR_EVICTOR_LRU_ONLY);
334          registerProp("je.evictor.nodesPerScan", ATTR_EVICTOR_NODES_PER_SCAN);
335          registerProp("je.log.fileMax", ATTR_DATABASE_LOG_FILE_MAX);
336          registerProp("java.util.logging.FileHandler.on",
337                       ATTR_LOGGING_FILE_HANDLER_ON);
338          registerProp("java.util.logging.level", ATTR_LOGGING_LEVEL);
339          registerProp("je.checkpointer.bytesInterval",
340                       ATTR_CHECKPOINTER_BYTES_INTERVAL);
341          registerProp("je.checkpointer.wakeupInterval",
342                       ATTR_CHECKPOINTER_WAKEUP_INTERVAL);
343          registerProp("je.lock.nLockTables", ATTR_NUM_LOCK_TABLES);
344          registerProp("je.cleaner.threads", ATTR_NUM_CLEANER_THREADS);
345        }
346        catch (Exception e)
347        {
348          if (debugEnabled())
349          {
350            TRACER.debugCaught(DebugLogLevel.ERROR, e);
351          }
352        }
353      }
354    
355    
356    
357      /**
358       * Create a JE environment configuration with default values.
359       *
360       * @return A JE environment config containing default values.
361       */
362      public static EnvironmentConfig defaultConfig()
363      {
364        EnvironmentConfig envConfig = new EnvironmentConfig();
365    
366        envConfig.setTransactional(true);
367        envConfig.setAllowCreate(true);
368    
369        // This property was introduced in JE 3.0.  Shared latches are now used on
370        // all internal nodes of the b-tree, which increases concurrency for many
371        // operations.
372        envConfig.setConfigParam("je.env.sharedLatches", "true");
373    
374        // This parameter was set to false while diagnosing a Berkeley DB JE bug.
375        // Normally cleansed log files are deleted, but if this is set false
376        // they are instead renamed from .jdb to .del.
377        envConfig.setConfigParam("je.cleaner.expunge", "true");
378    
379        return envConfig;
380      }
381    
382    
383    
384      /**
385       * Parse a configuration associated with a JE environment and create an
386       * environment config from it.
387       *
388       * @param cfg The configuration to be parsed.
389       * @return An environment config instance corresponding to the config entry.
390       * @throws ConfigException If there is an error in the provided configuration
391       * entry.
392       */
393      public static EnvironmentConfig parseConfigEntry(LocalDBBackendCfg cfg)
394           throws ConfigException
395      {
396        EnvironmentConfig envConfig = defaultConfig();
397    
398        // Handle the attributes that do not have a JE property.
399        envConfig.setTxnNoSync(cfg.isDBTxnNoSync());
400        envConfig.setTxnWriteNoSync(cfg.isDBTxnWriteNoSync());
401    
402        // Iterate through the config attributes associated with a JE property.
403        for (Map.Entry<String, String> mapEntry : attrMap.entrySet())
404        {
405          String jeProperty = mapEntry.getKey();
406          String attrName = mapEntry.getValue();
407    
408          String value = getPropertyValue(cfg, attrName);
409          envConfig.setConfigParam(jeProperty, value);
410        }
411    
412        // See if there are any native JE properties specified in the config
413        // and if so try to parse, evaluate and set them.
414        SortedSet<String> jeProperties = cfg.getJEProperty();
415        try {
416          envConfig = setJEProperties(envConfig, jeProperties, attrMap);
417        } catch (ConfigException e) {
418          throw e;
419        }
420    
421        return envConfig;
422      }
423    
424    
425    
426      /**
427       * Parse, validate and set native JE environment properties for
428       * a given environment config.
429       *
430       * @param  envConfig The JE environment config for which to set
431       *                   the properties.
432       * @param  jeProperties The JE environment properties to parse,
433       *                      validate and set.
434       * @param  configAttrMap Component supported JE properties to
435       *                       their configuration attributes map.
436       * @return An environment config instance with given properties
437       *         set.
438       * @throws ConfigException If there is an error while parsing,
439       *         validating and setting any of the properties provided.
440       */
441      public static EnvironmentConfig setJEProperties(EnvironmentConfig envConfig,
442        SortedSet<String> jeProperties, HashMap<String, String> configAttrMap)
443        throws ConfigException
444      {
445        if (jeProperties.isEmpty()) {
446          // return default config.
447          return envConfig;
448        }
449    
450        // Set to catch duplicate properties.
451        HashSet<String> uniqueJEProperties = new HashSet<String>();
452    
453        // Iterate through the config values associated with a JE property.
454        for (String jeEntry : jeProperties)
455        {
456          StringTokenizer st = new StringTokenizer(jeEntry, "=");
457          if (st.countTokens() == 2) {
458            String jePropertyName = st.nextToken();
459            String jePropertyValue = st.nextToken();
460            // Check if it is a duplicate.
461            if (uniqueJEProperties.contains(jePropertyName)) {
462              Message message = ERR_CONFIG_JE_DUPLICATE_PROPERTY.get(
463                  jePropertyName);
464                throw new ConfigException(message);
465            }
466            // Set JE property.
467            try {
468              envConfig.setConfigParam(jePropertyName, jePropertyValue);
469              // This is a special case that JE cannot validate before
470              // actually setting it. Validate it before it gets to JE.
471              if (jePropertyName.equals("java.util.logging.level")) {
472                java.util.logging.Level.parse(jePropertyValue);
473              }
474              // If this property shadows an existing config attribute.
475              if (configAttrMap.containsKey(jePropertyName)) {
476                Message message = ERR_CONFIG_JE_PROPERTY_SHADOWS_CONFIG.get(
477                  jePropertyName, attrMap.get(jePropertyName));
478                throw new ConfigException(message);
479              }
480              // Add this property to unique set.
481              uniqueJEProperties.add(jePropertyName);
482            } catch(IllegalArgumentException e) {
483              if (debugEnabled()) {
484                TRACER.debugCaught(DebugLogLevel.ERROR, e);
485              }
486              Message message =
487                ERR_CONFIG_JE_PROPERTY_INVALID.get(
488                jeEntry, e.getMessage());
489              throw new ConfigException(message, e.getCause());
490            }
491          } else {
492            Message message =
493              ERR_CONFIG_JE_PROPERTY_INVALID_FORM.get(jeEntry);
494            throw new ConfigException(message);
495          }
496        }
497    
498        return envConfig;
499      }
500    
501    
502    
503    }