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 legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2007-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2012-2015 ForgeRock AS.
026 */
027package org.forgerock.opendj.config.dsconfig;
028
029import static com.forgerock.opendj.cli.ArgumentConstants.*;
030import static com.forgerock.opendj.cli.CliMessages.*;
031import static com.forgerock.opendj.cli.DocGenerationHelper.*;
032import static com.forgerock.opendj.cli.Utils.*;
033import static com.forgerock.opendj.dsconfig.DsconfigMessages.*;
034import static com.forgerock.opendj.util.StaticUtils.*;
035
036import static org.forgerock.opendj.config.PropertyOption.*;
037import static org.forgerock.opendj.config.dsconfig.ArgumentExceptionFactory.*;
038import static org.forgerock.util.Utils.*;
039
040import java.io.BufferedReader;
041import java.io.BufferedWriter;
042import java.io.File;
043import java.io.FileReader;
044import java.io.FileWriter;
045import java.io.IOException;
046import java.io.InputStreamReader;
047import java.io.OutputStream;
048import java.io.PrintStream;
049import java.net.URL;
050import java.util.ArrayList;
051import java.util.Collection;
052import java.util.Collections;
053import java.util.Comparator;
054import java.util.Date;
055import java.util.Enumeration;
056import java.util.HashMap;
057import java.util.Iterator;
058import java.util.LinkedList;
059import java.util.List;
060import java.util.Map;
061import java.util.Properties;
062import java.util.Set;
063import java.util.SortedSet;
064import java.util.TreeMap;
065import java.util.TreeSet;
066
067import org.forgerock.i18n.LocalizableMessage;
068import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1;
069import org.forgerock.opendj.config.ACIPropertyDefinition;
070import org.forgerock.opendj.config.AbsoluteInheritedDefaultBehaviorProvider;
071import org.forgerock.opendj.config.AbstractManagedObjectDefinition;
072import org.forgerock.opendj.config.AdministratorAction;
073import org.forgerock.opendj.config.AdministratorAction.Type;
074import org.forgerock.opendj.config.AggregationPropertyDefinition;
075import org.forgerock.opendj.config.AliasDefaultBehaviorProvider;
076import org.forgerock.opendj.config.AttributeTypePropertyDefinition;
077import org.forgerock.opendj.config.BooleanPropertyDefinition;
078import org.forgerock.opendj.config.ClassPropertyDefinition;
079import org.forgerock.opendj.config.ConfigurationFramework;
080import org.forgerock.opendj.config.DNPropertyDefinition;
081import org.forgerock.opendj.config.DefaultBehaviorProvider;
082import org.forgerock.opendj.config.DefinedDefaultBehaviorProvider;
083import org.forgerock.opendj.config.DurationPropertyDefinition;
084import org.forgerock.opendj.config.DurationUnit;
085import org.forgerock.opendj.config.EnumPropertyDefinition;
086import org.forgerock.opendj.config.IPAddressMaskPropertyDefinition;
087import org.forgerock.opendj.config.IPAddressPropertyDefinition;
088import org.forgerock.opendj.config.InstantiableRelationDefinition;
089import org.forgerock.opendj.config.IntegerPropertyDefinition;
090import org.forgerock.opendj.config.ManagedObjectOption;
091import org.forgerock.opendj.config.PropertyDefinition;
092import org.forgerock.opendj.config.PropertyDefinitionVisitor;
093import org.forgerock.opendj.config.PropertyOption;
094import org.forgerock.opendj.config.RelationDefinition;
095import org.forgerock.opendj.config.RelationOption;
096import org.forgerock.opendj.config.RelativeInheritedDefaultBehaviorProvider;
097import org.forgerock.opendj.config.SetRelationDefinition;
098import org.forgerock.opendj.config.SizePropertyDefinition;
099import org.forgerock.opendj.config.StringPropertyDefinition;
100import org.forgerock.opendj.config.Tag;
101import org.forgerock.opendj.config.UndefinedDefaultBehaviorProvider;
102import org.forgerock.opendj.config.client.ManagedObjectDecodingException;
103import org.forgerock.opendj.config.client.MissingMandatoryPropertiesException;
104import org.forgerock.opendj.config.client.OperationRejectedException;
105import org.forgerock.opendj.config.server.ConfigException;
106import org.forgerock.opendj.ldap.DN;
107import org.forgerock.util.Utils;
108
109import com.forgerock.opendj.cli.Argument;
110import com.forgerock.opendj.cli.ArgumentException;
111import com.forgerock.opendj.cli.ArgumentGroup;
112import com.forgerock.opendj.cli.BooleanArgument;
113import com.forgerock.opendj.cli.CliConstants;
114import com.forgerock.opendj.cli.ClientException;
115import com.forgerock.opendj.cli.CommandBuilder;
116import com.forgerock.opendj.cli.CommonArguments;
117import com.forgerock.opendj.cli.ConnectionFactoryProvider;
118import com.forgerock.opendj.cli.ConsoleApplication;
119import com.forgerock.opendj.cli.Menu;
120import com.forgerock.opendj.cli.MenuBuilder;
121import com.forgerock.opendj.cli.MenuCallback;
122import com.forgerock.opendj.cli.MenuResult;
123import com.forgerock.opendj.cli.ReturnCode;
124import com.forgerock.opendj.cli.StringArgument;
125import com.forgerock.opendj.cli.SubCommand;
126import com.forgerock.opendj.cli.SubCommandArgumentParser;
127import com.forgerock.opendj.cli.SubCommandUsageHandler;
128import com.forgerock.opendj.cli.VersionHandler;
129
130/**
131 * This class provides a command-line tool which enables administrators to configure the Directory Server.
132 */
133public final class DSConfig extends ConsoleApplication {
134    /**
135     * This class provides additional information about subcommands for generated reference documentation.
136     */
137    private final class DSConfigSubCommandUsageHandler implements SubCommandUsageHandler {
138        /** Marker to open a DocBook XML paragraph. */
139        private String op = "<para>";
140        /** Marker to close a DocBook XML paragraph. */
141        private String cp = "</para>";
142
143        @Override
144        public String getArgumentAdditionalInfo(SubCommand sc, Argument a, String nameOption) {
145            StringBuilder sb = new StringBuilder();
146            final AbstractManagedObjectDefinition<?, ?> defn = getManagedObjectDefinition(sc);
147            if (isHidden(defn)) {
148                return "";
149            }
150            if (doesHandleProperties(a)) {
151                final LocalizableMessage name = defn.getUserFriendlyName();
152                sb.append(op).append(REF_DSCFG_ARG_ADDITIONAL_INFO.get(name, name, nameOption)).append(cp).append(EOL);
153            } else {
154                listSubtypes(sb, sc, a, defn);
155            }
156            return sb.toString();
157        }
158
159        private boolean isHidden(AbstractManagedObjectDefinition<?, ?> defn) {
160            return defn == null || defn.hasOption(ManagedObjectOption.HIDDEN);
161        }
162
163        private void listSubtypes(StringBuilder sb, SubCommand sc, Argument a,
164                                  AbstractManagedObjectDefinition<?, ?> defn) {
165            if (a.isHidden()) {
166                return;
167            }
168
169            final LocalizableMessage placeholder = a.getValuePlaceholder();
170
171            Map<String, Object> map = new HashMap<>();
172
173            final LocalizableMessage name = defn.getUserFriendlyName();
174            map.put("dependencies", REF_DSCFG_SUBTYPE_DEPENDENCIES.get(name, name, placeholder));
175            map.put("typesIntro", REF_DSCFG_SUBTYPE_TYPES_INTRO.get(name));
176
177            List<Map<String, Object>> children = new LinkedList<>();
178            for (AbstractManagedObjectDefinition<?, ?> childDefn : getLeafChildren(defn)) {
179                if (isHidden(childDefn)) {
180                    continue;
181                }
182                Map<String, Object> child = new HashMap<>();
183
184                child.put("name", childDefn.getName());
185                child.put("default", REF_DSCFG_CHILD_DEFAULT.get(placeholder, childDefn.getUserFriendlyName()));
186                child.put("enabled", REF_DSCFG_CHILD_ENABLED_BY_DEFAULT.get(propertyExists(childDefn, "enabled")));
187
188                final String link = getLink(getScriptName() + "-" + sc.getName() + "-" + childDefn.getName());
189                child.put("link", REF_DSCFG_CHILD_LINK.get(link, defn.getUserFriendlyName()));
190
191                children.add(child);
192            }
193            map.put("children", children);
194
195            applyTemplate(sb, "dscfgListSubtypes.ftl", map);
196        }
197
198        private boolean propertyExists(AbstractManagedObjectDefinition<?, ?> defn, String name) {
199            if (isHidden(defn)) {
200                return false;
201            }
202            try {
203                return defn.getPropertyDefinition(name) != null;
204            } catch (IllegalArgumentException e) {
205                return false;
206            }
207        }
208
209        @Override
210        public String getProperties(SubCommand sc) {
211            final AbstractManagedObjectDefinition<?, ?> defn = getManagedObjectDefinition(sc);
212            if (isHidden(defn)) {
213                return "";
214            }
215
216            StringBuilder sb = new StringBuilder();
217            for (AbstractManagedObjectDefinition<?, ?> childDefn : getLeafChildren(defn)) {
218                if (isHidden(childDefn)) {
219                    continue;
220                }
221                final List<PropertyDefinition<?>> props = new ArrayList<>(childDefn.getAllPropertyDefinitions());
222                Collections.sort(props);
223                Map<String, Object> map = new HashMap<>();
224                final String propPrefix = getScriptName() + "-" + sc.getName() + "-" + childDefn.getName();
225                map.put("id", propPrefix);
226                map.put("title", childDefn.getUserFriendlyName());
227                map.put("intro", REF_DSCFG_PROPS_INTRO.get(defn.getUserFriendlyPluralName(), childDefn.getName()));
228                map.put("list", toVariableList(props, defn));
229                applyTemplate(sb, "dscfgAppendProps.ftl", map);
230            }
231            return sb.toString();
232        }
233
234        private AbstractManagedObjectDefinition<?, ?> getManagedObjectDefinition(SubCommand sc) {
235            final SubCommandHandler sch = handlers.get(sc);
236            if (sch instanceof HelpSubCommandHandler) {
237                return null;
238            }
239            final RelationDefinition<?, ?> rd = getRelationDefinition(sch);
240            if (isHidden(rd)) {
241                return null;
242            }
243            return rd.getChildDefinition();
244        }
245
246        private boolean isHidden(RelationDefinition defn) {
247            return defn == null || defn.hasOption(RelationOption.HIDDEN);
248        }
249
250        private List<AbstractManagedObjectDefinition<?, ?>> getLeafChildren(
251                AbstractManagedObjectDefinition<?, ?> defn) {
252            final ArrayList<AbstractManagedObjectDefinition<?, ?>> results = new ArrayList<>();
253            addIfLeaf(results, defn);
254            Collections.sort(results, new Comparator<AbstractManagedObjectDefinition<?, ?>>() {
255                @Override
256                public int compare(AbstractManagedObjectDefinition<?, ?> o1, AbstractManagedObjectDefinition<?, ?> o2) {
257                    return o1.getName().compareTo(o2.getName());
258                }
259            });
260            return results;
261        }
262
263        private void addIfLeaf(final Collection<AbstractManagedObjectDefinition<?, ?>> results,
264                final AbstractManagedObjectDefinition<?, ?> defn) {
265            if (defn.getChildren().isEmpty()) {
266                results.add(defn);
267            } else {
268                for (AbstractManagedObjectDefinition<?, ?> child : defn.getChildren()) {
269                    addIfLeaf(results, child);
270                }
271            }
272        }
273
274        private RelationDefinition<?, ?> getRelationDefinition(final SubCommandHandler sch) {
275            if (sch instanceof CreateSubCommandHandler) {
276                return ((CreateSubCommandHandler<?, ?>) sch).getRelationDefinition();
277            } else if (sch instanceof DeleteSubCommandHandler) {
278                return ((DeleteSubCommandHandler) sch).getRelationDefinition();
279            } else if (sch instanceof ListSubCommandHandler) {
280                return ((ListSubCommandHandler) sch).getRelationDefinition();
281            } else if (sch instanceof GetPropSubCommandHandler) {
282                return ((GetPropSubCommandHandler) sch).getRelationDefinition();
283            } else if (sch instanceof SetPropSubCommandHandler) {
284                return ((SetPropSubCommandHandler) sch).getRelationDefinition();
285            }
286            return null;
287        }
288
289        private String toVariableList(List<PropertyDefinition<?>> props, AbstractManagedObjectDefinition<?, ?> defn) {
290            StringBuilder b = new StringBuilder();
291            Map<String, Object> map = new HashMap<>();
292
293            List<Map<String, Object>> properties = new LinkedList<>();
294            for (PropertyDefinition<?> prop : props) {
295                if (prop.hasOption(HIDDEN)) {
296                    continue;
297                }
298                Map<String, Object> property = new HashMap<>();
299                property.put("term", prop.getName());
300                property.put("descTitle", REF_TITLE_DESCRIPTION.get());
301                property.put("description", getDescriptionString(prop));
302
303                final StringBuilder sb = new StringBuilder();
304                appendDefaultBehavior(sb, prop);
305                appendAllowedValues(sb, prop);
306                appendVarListEntry(sb, REF_DSCFG_PROPS_LABEL_MULTI_VALUED.get().toString(), getYN(prop, MULTI_VALUED));
307                appendVarListEntry(sb, REF_DSCFG_PROPS_LABEL_REQUIRED.get().toString(), getYN(prop, MANDATORY));
308                appendVarListEntry(sb, REF_DSCFG_PROPS_LABEL_ADMIN_ACTION_REQUIRED.get().toString(),
309                        getAdminActionRequired(prop, defn));
310                appendVarListEntry(sb, REF_DSCFG_PROPS_LABEL_ADVANCED_PROPERTY.get().toString(),
311                        getYNAdvanced(prop, ADVANCED));
312                appendVarListEntry(sb, REF_DSCFG_PROPS_LABEL_READ_ONLY.get().toString(), getYN(prop, READ_ONLY));
313                property.put("list", sb.toString());
314
315                properties.add(property);
316            }
317            map.put("properties", properties);
318
319            applyTemplate(b, "dscfgVariableList.ftl", map);
320            return b.toString();
321        }
322
323        private StringBuilder appendVarListEntry(StringBuilder b, String term, Object definition) {
324            Map<String, Object> map = new HashMap<>();
325            map.put("term", term);
326            map.put("definition", definition);
327            applyTemplate(b, "dscfgVarListEntry.ftl", map);
328            return b;
329        }
330
331        private void appendDefaultBehavior(StringBuilder b, PropertyDefinition<?> prop) {
332            StringBuilder sb = new StringBuilder();
333            appendDefaultBehaviorString(sb, prop);
334            appendVarListEntry(b, REF_DSCFG_PROPS_LABEL_DEFAULT_VALUE.get().toString(), sb.toString());
335        }
336
337        private void appendAllowedValues(StringBuilder b, PropertyDefinition<?> prop) {
338            StringBuilder sb = new StringBuilder();
339            appendSyntax(sb, prop);
340            appendVarListEntry(b, REF_DSCFG_PROPS_LABEL_ALLOWED_VALUES.get().toString(), sb.toString());
341        }
342
343        private Object getDescriptionString(PropertyDefinition<?> prop) {
344            return ((prop.getSynopsis() != null) ? prop.getSynopsis() + " " : "")
345                    + ((prop.getDescription() != null) ? prop.getDescription() : "");
346        }
347
348        private String getAdminActionRequired(PropertyDefinition<?> prop, AbstractManagedObjectDefinition<?, ?> defn) {
349            final AdministratorAction adminAction = prop.getAdministratorAction();
350            if (adminAction != null) {
351                final LocalizableMessage synopsis = adminAction.getSynopsis();
352                final Type actionType = adminAction.getType();
353                final StringBuilder action = new StringBuilder();
354                if (actionType == Type.COMPONENT_RESTART) {
355                    action.append(op)
356                            .append(REF_DSCFG_ADMIN_ACTION_COMPONENT_RESTART.get(defn.getUserFriendlyName()))
357                            .append(cp);
358                } else if (actionType == Type.SERVER_RESTART) {
359                    action.append(op).append(REF_DSCFG_ADMIN_ACTION_SERVER_RESTART.get()).append(cp);
360                } else if (actionType == Type.NONE) {
361                    action.append(op).append(REF_DSCFG_ADMIN_ACTION_NONE.get()).append(cp);
362                }
363                if (synopsis != null) {
364                    action.append(op).append(synopsis).append(cp);
365                }
366                return action.toString();
367            }
368            return op + REF_DSCFG_ADMIN_ACTION_NONE.get() + cp;
369        }
370
371        private String getYN(PropertyDefinition<?> prop, PropertyOption option) {
372            LocalizableMessage msg = prop.hasOption(option) ? REF_DSCFG_PROP_YES.get() : REF_DSCFG_PROP_NO.get();
373            return op + msg + cp;
374        }
375
376        private String getYNAdvanced(PropertyDefinition<?> prop, PropertyOption option) {
377            LocalizableMessage msg = prop.hasOption(option)
378                    ? REF_DSCFG_PROP_YES_ADVANCED.get() : REF_DSCFG_PROP_NO.get();
379            return op + msg + cp;
380        }
381
382        private void appendDefaultBehaviorString(StringBuilder b, PropertyDefinition<?> prop) {
383            final DefaultBehaviorProvider<?> defaultBehavior = prop.getDefaultBehaviorProvider();
384            if (defaultBehavior instanceof UndefinedDefaultBehaviorProvider) {
385                b.append(op).append(REF_DSCFG_DEFAULT_BEHAVIOR_NONE.get()).append(cp).append(EOL);
386            } else if (defaultBehavior instanceof DefinedDefaultBehaviorProvider) {
387                DefinedDefaultBehaviorProvider<?> behavior = (DefinedDefaultBehaviorProvider<?>) defaultBehavior;
388                final Collection<String> defaultValues = behavior.getDefaultValues();
389                if (defaultValues.isEmpty()) {
390                    b.append(op).append(REF_DSCFG_DEFAULT_BEHAVIOR_NONE.get()).append(cp).append(EOL);
391                } else if (defaultValues.size() == 1) {
392                    b.append(op).append(REF_DSCFG_DEFAULT_BEHAVIOR.get(defaultValues.iterator().next()))
393                            .append(cp).append(EOL);
394                } else {
395                    final Iterator<String> it = defaultValues.iterator();
396                    b.append(op).append(REF_DSCFG_DEFAULT_BEHAVIOR.get(it.next())).append(cp);
397                    for (; it.hasNext();) {
398                        b.append(EOL).append(op).append(REF_DSCFG_DEFAULT_BEHAVIOR.get(it.next())).append(cp);
399                    }
400                    b.append(EOL);
401                }
402            } else if (defaultBehavior instanceof AliasDefaultBehaviorProvider) {
403                AliasDefaultBehaviorProvider<?> behavior = (AliasDefaultBehaviorProvider<?>) defaultBehavior;
404                b.append(op).append(REF_DSCFG_DEFAULT_BEHAVIOR.get(behavior.getSynopsis())).append(cp).append(EOL);
405            } else if (defaultBehavior instanceof RelativeInheritedDefaultBehaviorProvider) {
406                final RelativeInheritedDefaultBehaviorProvider<?> behavior =
407                        (RelativeInheritedDefaultBehaviorProvider<?>) defaultBehavior;
408                appendDefaultBehaviorString(b,
409                        behavior.getManagedObjectDefinition().getPropertyDefinition(behavior.getPropertyName()));
410            } else if (defaultBehavior instanceof AbsoluteInheritedDefaultBehaviorProvider) {
411                final AbsoluteInheritedDefaultBehaviorProvider<?> behavior =
412                        (AbsoluteInheritedDefaultBehaviorProvider<?>) defaultBehavior;
413                appendDefaultBehaviorString(b,
414                        behavior.getManagedObjectDefinition().getPropertyDefinition(behavior.getPropertyName()));
415            }
416        }
417
418        private void appendSyntax(final StringBuilder b, PropertyDefinition<?> prop) {
419            // Create a visitor for performing syntax specific processing.
420            PropertyDefinitionVisitor<String, Void> visitor = new PropertyDefinitionVisitor<String, Void>() {
421
422                @Override
423                public String visitACI(ACIPropertyDefinition prop, Void p) {
424                    b.append(op).append(REF_DSCFG_ACI_SYNTAX_REL_URL.get()).append(cp).append(EOL);
425                    return null;
426                }
427
428                @Override
429                public String visitAggregation(AggregationPropertyDefinition prop, Void p) {
430                    b.append(op);
431                    final RelationDefinition<?, ?> rel = prop.getRelationDefinition();
432                    if (isHidden(rel)) {
433                        return null;
434                    }
435                    final String relFriendlyName = rel.getUserFriendlyName().toString();
436                    b.append(REF_DSCFG_AGGREGATION.get(relFriendlyName)).append(". ");
437                    final LocalizableMessage synopsis = prop.getSourceConstraintSynopsis();
438                    if (synopsis != null) {
439                        b.append(synopsis);
440                    }
441                    b.append(cp).append(EOL);
442                    return null;
443                }
444
445                @Override
446                public String visitAttributeType(AttributeTypePropertyDefinition prop, Void p) {
447                    b.append(op).append(REF_DSCFG_ANY_ATTRIBUTE.get()).append(".").append(cp).append(EOL);
448                    return null;
449                }
450
451                @Override
452                public String visitBoolean(BooleanPropertyDefinition prop, Void p) {
453                    b.append(op).append("true").append(cp).append(EOL);
454                    b.append(op).append("false").append(cp).append(EOL);
455                    return null;
456                }
457
458                @Override
459                public String visitClass(ClassPropertyDefinition prop, Void p) {
460                    b.append(op).append(REF_DSCFG_JAVA_PLUGIN.get()).append(" ")
461                            .append(Utils.joinAsString(EOL, prop.getInstanceOfInterface())).append(cp).append(EOL);
462                    return null;
463                }
464
465                @Override
466                public String visitDN(DNPropertyDefinition prop, Void p) {
467                    b.append(op).append(REF_DSCFG_VALID_DN.get());
468                    final DN baseDN = prop.getBaseDN();
469                    if (baseDN != null) {
470                        b.append(": ").append(baseDN);
471                    } else {
472                        b.append(".");
473                    }
474                    b.append(cp).append(EOL);
475                    return null;
476                }
477
478                @Override
479                public String visitDuration(DurationPropertyDefinition prop, Void p) {
480                    b.append(REF_DSCFG_DURATION_SYNTAX_REL_URL.get()).append(EOL);
481                    b.append(op);
482                    if (prop.isAllowUnlimited()) {
483                        b.append(REF_DSCFG_ALLOW_UNLIMITED.get()).append(" ");
484                    }
485                    if (prop.getMaximumUnit() != null) {
486                        final String maxUnitName = prop.getMaximumUnit().getLongName();
487                        b.append(REF_DSCFG_DURATION_MAX_UNIT.get(maxUnitName)).append(".");
488                    }
489                    final DurationUnit baseUnit = prop.getBaseUnit();
490                    final long lowerLimit = valueOf(baseUnit, prop.getLowerLimit());
491                    final String unitName = baseUnit.getLongName();
492                    b.append(REF_DSCFG_DURATION_LOWER_LIMIT.get(lowerLimit, unitName)).append(".");
493                    if (prop.getUpperLimit() != null) {
494                        final long upperLimit = valueOf(baseUnit, prop.getUpperLimit());
495                        b.append(REF_DSCFG_DURATION_UPPER_LIMIT.get(upperLimit, unitName)).append(".");
496                    }
497                    b.append(cp).append(EOL);
498                    return null;
499                }
500
501                private long valueOf(final DurationUnit baseUnit, long upperLimit) {
502                    return Double.valueOf(baseUnit.fromMilliSeconds(upperLimit)).longValue();
503                }
504
505                @Override
506                public String visitEnum(EnumPropertyDefinition prop, Void p) {
507                    b.append("<variablelist>").append(EOL);
508                    final Class<?> en = prop.getEnumClass();
509                    final Object[] constants = en.getEnumConstants();
510                    for (Object enumConstant : constants) {
511                        final LocalizableMessage valueSynopsis = prop.getValueSynopsis((Enum) enumConstant);
512                        appendVarListEntry(b, enumConstant.toString(), op + valueSynopsis + cp);
513                    }
514                    b.append("</variablelist>").append(EOL);
515                    return null;
516                }
517
518                @Override
519                public String visitInteger(IntegerPropertyDefinition prop, Void p) {
520                    b.append(op).append(REF_DSCFG_INT.get()).append(". ")
521                            .append(REF_DSCFG_INT_LOWER_LIMIT.get(prop.getLowerLimit())).append(".");
522                    if (prop.getUpperLimit() != null) {
523                        b.append(" ").append(REF_DSCFG_INT_UPPER_LIMIT.get(prop.getUpperLimit())).append(".");
524                    }
525                    if (prop.isAllowUnlimited()) {
526                        b.append(" ").append(REF_DSCFG_ALLOW_UNLIMITED.get());
527                    }
528                    if (prop.getUnitSynopsis() != null) {
529                        b.append(" ").append(REF_DSCFG_INT_UNIT.get(prop.getUnitSynopsis())).append(".");
530                    }
531                    b.append(cp).append(EOL);
532                    return null;
533                }
534
535                @Override
536                public String visitIPAddress(IPAddressPropertyDefinition prop, Void p) {
537                    b.append(op).append(REF_DSCFG_IP_ADDRESS.get()).append(cp).append(EOL);
538                    return null;
539                }
540
541                @Override
542                public String visitIPAddressMask(IPAddressMaskPropertyDefinition prop, Void p) {
543                    b.append(op).append(REF_DSCFG_IP_ADDRESS_MASK.get()).append(cp).append(EOL);
544                    return null;
545                }
546
547                @Override
548                public String visitSize(SizePropertyDefinition prop, Void p) {
549                    b.append(op);
550                    if (prop.getLowerLimit() != 0) {
551                        b.append(REF_DSCFG_INT_LOWER_LIMIT.get(prop.getLowerLimit())).append(".");
552                    }
553                    if (prop.getUpperLimit() != null) {
554                        b.append(REF_DSCFG_INT_UPPER_LIMIT.get(prop.getUpperLimit())).append(".");
555                    }
556                    if (prop.isAllowUnlimited()) {
557                        b.append(REF_DSCFG_ALLOW_UNLIMITED.get());
558                    }
559                    b.append(cp).append(EOL);
560                    return null;
561                }
562
563                @Override
564                public String visitString(StringPropertyDefinition prop, Void p) {
565                    b.append(op);
566                    if (prop.getPatternSynopsis() != null) {
567                        b.append(prop.getPatternSynopsis());
568                    } else {
569                        b.append(REF_DSCFG_STRING.get());
570                    }
571                    b.append(cp).append(EOL);
572                    return null;
573                }
574
575                @Override
576                public String visitUnknown(PropertyDefinition prop, Void p) {
577                    b.append(op).append(REF_DSCFG_UNKNOWN.get()).append(cp).append(EOL);
578                    return null;
579                }
580            };
581
582            // Invoke the visitor against the property definition.
583            prop.accept(visitor, null);
584        }
585
586        private String getLink(String target) {
587            return " <xref linkend=\"" + target + "\" />";
588        }
589    }
590
591    /** The name of this tool. */
592    static final String DSCONFIGTOOLNAME = "dsconfig";
593
594    /** The name of a command-line script used to launch an administrative tool. */
595    static final String PROPERTY_SCRIPT_NAME = "org.opends.server.scriptName";
596
597    /** A menu call-back which runs a sub-command interactively. */
598    private class SubCommandHandlerMenuCallback implements MenuCallback<Integer> {
599        /** The sub-command handler. */
600        private final SubCommandHandler handler;
601
602        /**
603         * Creates a new sub-command handler call-back.
604         *
605         * @param handler
606         *            The sub-command handler.
607         */
608        public SubCommandHandlerMenuCallback(SubCommandHandler handler) {
609            this.handler = handler;
610        }
611
612        @Override
613        public MenuResult<Integer> invoke(ConsoleApplication app) throws ClientException {
614            try {
615                final MenuResult<Integer> result = handler.run(app, factory);
616                if (result.isQuit()) {
617                    return result;
618                }
619                if (result.isSuccess() && isInteractive() && handler.isCommandBuilderUseful()) {
620                    printCommandBuilder(getCommandBuilder(handler));
621                }
622                // Success or cancel.
623                app.println();
624                app.pressReturnToContinue();
625                return MenuResult.again();
626            } catch (ArgumentException e) {
627                app.errPrintln(e.getMessageObject());
628                return MenuResult.success(1);
629            } catch (ClientException e) {
630                app.errPrintln(e.getMessageObject());
631                return MenuResult.success(e.getReturnCode());
632            }
633        }
634    }
635
636    /** The interactive mode sub-menu implementation. */
637    private class SubMenuCallback implements MenuCallback<Integer> {
638        /** The menu. */
639        private final Menu<Integer> menu;
640
641        /**
642         * Creates a new sub-menu implementation.
643         *
644         * @param app
645         *            The console application.
646         * @param rd
647         *            The relation definition.
648         * @param ch
649         *            The optional create sub-command.
650         * @param dh
651         *            The optional delete sub-command.
652         * @param lh
653         *            The optional list sub-command.
654         * @param sh
655         *            The option set-prop sub-command.
656         */
657        public SubMenuCallback(ConsoleApplication app, RelationDefinition<?, ?> rd, CreateSubCommandHandler<?, ?> ch,
658                DeleteSubCommandHandler dh, ListSubCommandHandler lh, SetPropSubCommandHandler sh) {
659            LocalizableMessage userFriendlyName = rd.getUserFriendlyName();
660
661            LocalizableMessage userFriendlyPluralName = null;
662            if (rd instanceof InstantiableRelationDefinition<?, ?>) {
663                InstantiableRelationDefinition<?, ?> ir = (InstantiableRelationDefinition<?, ?>) rd;
664                userFriendlyPluralName = ir.getUserFriendlyPluralName();
665            } else if (rd instanceof SetRelationDefinition<?, ?>) {
666                SetRelationDefinition<?, ?> sr = (SetRelationDefinition<?, ?>) rd;
667                userFriendlyPluralName = sr.getUserFriendlyPluralName();
668            }
669
670            final MenuBuilder<Integer> builder = new MenuBuilder<>(app);
671
672            builder.setTitle(INFO_DSCFG_HEADING_COMPONENT_MENU_TITLE.get(userFriendlyName));
673            builder.setPrompt(INFO_DSCFG_HEADING_COMPONENT_MENU_PROMPT.get());
674
675            if (lh != null) {
676                final SubCommandHandlerMenuCallback callback = new SubCommandHandlerMenuCallback(lh);
677                final LocalizableMessage msg = getMsg(
678                    INFO_DSCFG_OPTION_COMPONENT_MENU_LIST_SINGULAR, userFriendlyName,
679                    INFO_DSCFG_OPTION_COMPONENT_MENU_LIST_PLURAL, userFriendlyPluralName);
680                builder.addNumberedOption(msg, callback);
681            }
682
683            if (ch != null) {
684                final SubCommandHandlerMenuCallback callback = new SubCommandHandlerMenuCallback(ch);
685                builder.addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_CREATE.get(userFriendlyName), callback);
686            }
687
688            if (sh != null) {
689                final SubCommandHandlerMenuCallback callback = new SubCommandHandlerMenuCallback(sh);
690                final LocalizableMessage msg = getMsg(
691                    INFO_DSCFG_OPTION_COMPONENT_MENU_MODIFY_SINGULAR, userFriendlyName,
692                    INFO_DSCFG_OPTION_COMPONENT_MENU_MODIFY_PLURAL, userFriendlyPluralName);
693                builder.addNumberedOption(msg, callback);
694            }
695
696            if (dh != null) {
697                final SubCommandHandlerMenuCallback callback = new SubCommandHandlerMenuCallback(dh);
698                builder.addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_DELETE.get(userFriendlyName), callback);
699            }
700
701            builder.addBackOption(true);
702            builder.addQuitOption();
703
704            this.menu = builder.toMenu();
705        }
706
707        private LocalizableMessage getMsg(Arg1<Object> singularMsg, LocalizableMessage userFriendlyName,
708                Arg1<Object> pluralMsg, LocalizableMessage userFriendlyPluralName) {
709            return userFriendlyPluralName != null
710                ? pluralMsg.get(userFriendlyPluralName)
711                : singularMsg.get(userFriendlyName);
712        }
713
714        @Override
715        public final MenuResult<Integer> invoke(ConsoleApplication app) throws ClientException {
716            try {
717                app.println();
718                app.println();
719
720                final MenuResult<Integer> result = menu.run();
721                if (result.isCancel()) {
722                    return MenuResult.again();
723                }
724                return result;
725            } catch (ClientException e) {
726                app.errPrintln(e.getMessageObject());
727                return MenuResult.success(1);
728            }
729        }
730    }
731
732    /**
733     * The type name which will be used for the most generic managed object types when they are instantiable and
734     * intended for customization only.
735     */
736    public static final String CUSTOM_TYPE = "custom";
737
738    /**
739     * The type name which will be used for the most generic managed object types when they are instantiable and not
740     * intended for customization.
741     */
742    public static final String GENERIC_TYPE = "generic";
743
744    /**
745     * Prints the provided error message if the provided application is interactive,
746     * throws a {@link ClientException} with provided error code and message otherwise.
747     *
748     * @param <T>
749     *            The generic type parameter of the returned {@link MenuResult}
750     * @param app
751     *            The console application where the message should be printed.
752     * @param msg
753     *            The human readable error message.
754     * @param errorCode
755     *            The operation error code.
756     * @return A generic cancel menu result if application is interactive.
757     * @throws ClientException
758     *             If the application is not interactive.
759     */
760    static <T> MenuResult<T> interactivePrintOrThrowError(ConsoleApplication app,
761        LocalizableMessage msg, ReturnCode errorCode) throws ClientException {
762        if (!app.isInteractive()) {
763            throw new ClientException(errorCode, msg);
764        }
765        app.errPrintln();
766        app.errPrintln(msg);
767        return MenuResult.cancel();
768    }
769
770    private long sessionStartTime;
771    private boolean sessionStartTimePrinted;
772    private int sessionEquivalentOperationNumber;
773
774    /**
775     * Provides the command-line arguments to the main application for processing.
776     *
777     * @param args
778     *            The set of command-line arguments provided to this program.
779     */
780    public static void main(String[] args) {
781        int exitCode = main(args, System.out, System.err);
782        if (exitCode != ReturnCode.SUCCESS.get()) {
783            System.exit(filterExitCode(exitCode));
784        }
785    }
786
787    /**
788     * Provides the command-line arguments to the main application for processing and returns the exit code as an
789     * integer.
790     *
791     * @param args
792     *            The set of command-line arguments provided to this program.
793     * @param outStream
794     *            The output stream for standard output.
795     * @param errStream
796     *            The output stream for standard error.
797     * @return Zero to indicate that the program completed successfully, or non-zero to indicate that an error occurred.
798     */
799    public static int main(String[] args, OutputStream outStream, OutputStream errStream) {
800        final DSConfig app = new DSConfig(outStream, errStream);
801        app.sessionStartTime = System.currentTimeMillis();
802
803        if (!ConfigurationFramework.getInstance().isInitialized()) {
804            try {
805                ConfigurationFramework.getInstance().initialize();
806            } catch (ConfigException e) {
807                app.errPrintln(e.getMessageObject());
808                return ReturnCode.ERROR_INITIALIZING_SERVER.get();
809            }
810        }
811
812        // Run the application.
813        return app.run(args);
814    }
815
816    /** The factory which the application should use to retrieve its management context. */
817    private LDAPManagementContextFactory factory;
818
819    /** Flag indicating whether or not the global arguments have already been initialized. */
820    private boolean globalArgumentsInitialized;
821
822    /** The sub-command handler factory. */
823    private SubCommandHandlerFactory handlerFactory;
824    /** Mapping of sub-commands to their implementations. */
825    private final Map<SubCommand, SubCommandHandler> handlers = new HashMap<>();
826    /** Indicates whether or not a sub-command was provided. */
827    private boolean hasSubCommand = true;
828
829    /** The command-line argument parser. */
830    private final SubCommandArgumentParser parser;
831
832    /** The argument which should be used to request advanced mode. */
833    private BooleanArgument advancedModeArgument;
834    /** The argument which should be used to request non interactive behavior. */
835    private BooleanArgument noPromptArgument;
836    /** The argument that the user must set to display the equivalent non-interactive mode argument. */
837    private BooleanArgument displayEquivalentArgument;
838    /** The argument that allows the user to dump the equivalent non-interactive command to a file. */
839    private StringArgument equivalentCommandFileArgument;
840
841    /** The argument which should be used to request quiet output. */
842    private BooleanArgument quietArgument;
843    /** The argument which should be used to request script-friendly output. */
844    private BooleanArgument scriptFriendlyArgument;
845    /** The argument which should be used to request usage information. */
846    private BooleanArgument showUsageArgument;
847    /** The argument which should be used to request verbose output. */
848    private BooleanArgument verboseArgument;
849
850    /** The argument which should be used to read dsconfig commands from standard input. */
851    private BooleanArgument batchArgument;
852    /** The argument which should be used to read dsconfig commands from a file. */
853    private StringArgument batchFileArgument;
854
855    /** The argument which should be used to indicate the properties file. */
856    private StringArgument propertiesFileArgument;
857    /** The argument which should be used to indicate that we will not look for properties file. */
858    private BooleanArgument noPropertiesFileArgument;
859
860    /**
861     * Creates a new DSConfig application instance.
862     *
863     * @param out
864     *            The application output stream.
865     * @param err
866     *            The application error stream.
867     */
868    private DSConfig(OutputStream out, OutputStream err) {
869        super(new PrintStream(out), new PrintStream(err));
870
871        this.parser = new SubCommandArgumentParser(getClass().getName(), INFO_DSCFG_TOOL_DESCRIPTION.get(), false);
872        this.parser.setShortToolDescription(REF_SHORT_DESC_DSCONFIG.get());
873        this.parser.setDocToolDescriptionSupplement(REF_DSCFG_DOC_TOOL_DESCRIPTION.get());
874        this.parser.setDocSubcommandsDescriptionSupplement(REF_DSCFG_DOC_SUBCOMMANDS_DESCRIPTION.get());
875        this.parser.setVersionHandler(new VersionHandler() {
876            @Override
877            public void printVersion() {
878                System.out.println(getVersionString());
879            }
880
881            private String getVersionString() {
882                try {
883                    final Enumeration<URL> resources = getClass().getClassLoader().getResources(
884                            "META-INF/maven/org.forgerock.opendj/opendj-config/pom.properties");
885                    while (resources.hasMoreElements()) {
886                        final Properties props = new Properties();
887                        props.load(resources.nextElement().openStream());
888                        return (String) props.get("version");
889                    }
890                } catch (IOException e) {
891                    errPrintln(LocalizableMessage.raw(e.getMessage()));
892                }
893                return "";
894            }
895        });
896        if (System.getProperty("org.forgerock.opendj.gendoc") != null) {
897            this.parser.setUsageHandler(new DSConfigSubCommandUsageHandler());
898        }
899    }
900
901    @Override
902    public boolean isAdvancedMode() {
903        return advancedModeArgument.isPresent();
904    }
905
906    @Override
907    public boolean isInteractive() {
908        return !noPromptArgument.isPresent();
909    }
910
911    @Override
912    public boolean isMenuDrivenMode() {
913        return !hasSubCommand;
914    }
915
916    @Override
917    public boolean isQuiet() {
918        return quietArgument.isPresent();
919    }
920
921    @Override
922    public boolean isScriptFriendly() {
923        return scriptFriendlyArgument.isPresent();
924    }
925
926    @Override
927    public boolean isVerbose() {
928        return verboseArgument.isPresent();
929    }
930
931    /**
932     * Registers the global arguments with the argument parser.
933     *
934     * @throws ArgumentException
935     *             If a global argument could not be registered.
936     */
937    private void initializeGlobalArguments() throws ArgumentException {
938        if (!globalArgumentsInitialized) {
939            verboseArgument = CommonArguments.getVerbose();
940            quietArgument = CommonArguments.getQuiet();
941            scriptFriendlyArgument = CommonArguments.getScriptFriendly();
942            noPromptArgument = CommonArguments.getNoPrompt();
943            advancedModeArgument = CommonArguments.getAdvancedMode();
944            showUsageArgument = CommonArguments.getShowUsage();
945
946            batchArgument = new BooleanArgument(OPTION_LONG_BATCH, null, OPTION_LONG_BATCH,
947                    INFO_DESCRIPTION_BATCH.get());
948
949            batchFileArgument = new StringArgument(OPTION_LONG_BATCH_FILE_PATH, OPTION_SHORT_BATCH_FILE_PATH,
950                    OPTION_LONG_BATCH_FILE_PATH, false, false, true, INFO_BATCH_FILE_PATH_PLACEHOLDER.get(), null,
951                    null, INFO_DESCRIPTION_BATCH_FILE_PATH.get());
952
953            displayEquivalentArgument = new BooleanArgument(OPTION_LONG_DISPLAY_EQUIVALENT, null,
954                    OPTION_LONG_DISPLAY_EQUIVALENT, INFO_DSCFG_DESCRIPTION_DISPLAY_EQUIVALENT.get());
955
956            equivalentCommandFileArgument = new StringArgument(OPTION_LONG_EQUIVALENT_COMMAND_FILE_PATH, null,
957                    OPTION_LONG_EQUIVALENT_COMMAND_FILE_PATH, false, false, true, INFO_PATH_PLACEHOLDER.get(), null,
958                    null, INFO_DSCFG_DESCRIPTION_EQUIVALENT_COMMAND_FILE_PATH.get());
959
960            propertiesFileArgument = new StringArgument("propertiesFilePath", null, OPTION_LONG_PROP_FILE_PATH, false,
961                    false, true, INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
962                    INFO_DESCRIPTION_PROP_FILE_PATH.get());
963
964            noPropertiesFileArgument = new BooleanArgument("noPropertiesFileArgument", null, OPTION_LONG_NO_PROP_FILE,
965                    INFO_DESCRIPTION_NO_PROP_FILE.get());
966
967            // Register the global arguments.
968
969            ArgumentGroup toolOptionsGroup = new ArgumentGroup(INFO_DSCFG_DESCRIPTION_OPTIONS_ARGS.get(), 2);
970            parser.addGlobalArgument(advancedModeArgument, toolOptionsGroup);
971
972            parser.addGlobalArgument(showUsageArgument);
973            parser.setUsageArgument(showUsageArgument, getOutputStream());
974            parser.addGlobalArgument(verboseArgument);
975            parser.addGlobalArgument(quietArgument);
976            parser.addGlobalArgument(scriptFriendlyArgument);
977            parser.addGlobalArgument(noPromptArgument);
978            parser.addGlobalArgument(batchArgument);
979            parser.addGlobalArgument(batchFileArgument);
980            parser.addGlobalArgument(displayEquivalentArgument);
981            parser.addGlobalArgument(equivalentCommandFileArgument);
982            parser.addGlobalArgument(propertiesFileArgument);
983            parser.setFilePropertiesArgument(propertiesFileArgument);
984            parser.addGlobalArgument(noPropertiesFileArgument);
985            parser.setNoPropertiesFileArgument(noPropertiesFileArgument);
986
987            globalArgumentsInitialized = true;
988        }
989    }
990
991    /**
992     * Registers the sub-commands with the argument parser. This method uses the administration framework introspection
993     * APIs to determine the overall structure of the command-line.
994     *
995     * @throws ArgumentException
996     *             If a sub-command could not be created.
997     */
998    private void initializeSubCommands() throws ArgumentException {
999        if (handlerFactory == null) {
1000            handlerFactory = new SubCommandHandlerFactory(parser);
1001
1002            final Comparator<SubCommand> c = new Comparator<SubCommand>() {
1003
1004                @Override
1005                public int compare(SubCommand o1, SubCommand o2) {
1006                    return o1.getName().compareTo(o2.getName());
1007                }
1008            };
1009
1010            Map<Tag, SortedSet<SubCommand>> groups = new TreeMap<>();
1011            SortedSet<SubCommand> allSubCommands = new TreeSet<>(c);
1012            for (SubCommandHandler handler : handlerFactory.getAllSubCommandHandlers()) {
1013                SubCommand sc = handler.getSubCommand();
1014
1015                handlers.put(sc, handler);
1016                allSubCommands.add(sc);
1017
1018                // Add the sub-command to its groups.
1019                for (Tag tag : handler.getTags()) {
1020                    SortedSet<SubCommand> group = groups.get(tag);
1021                    if (group == null) {
1022                        group = new TreeSet<>(c);
1023                        groups.put(tag, group);
1024                    }
1025                    group.add(sc);
1026                }
1027            }
1028
1029            // Register the usage arguments.
1030            for (Map.Entry<Tag, SortedSet<SubCommand>> group : groups.entrySet()) {
1031                Tag tag = group.getKey();
1032                SortedSet<SubCommand> subCommands = group.getValue();
1033
1034                String option = OPTION_LONG_HELP + "-" + tag.getName();
1035                String synopsis = tag.getSynopsis().toString().toLowerCase();
1036                BooleanArgument arg = new BooleanArgument(option, null, option,
1037                        INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE.get(synopsis));
1038
1039                parser.addGlobalArgument(arg);
1040                parser.setUsageGroupArgument(arg, subCommands);
1041            }
1042
1043            // Register the --help-all argument.
1044            String option = OPTION_LONG_HELP + "-all";
1045            BooleanArgument arg = new BooleanArgument(option, null, option,
1046                    INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_ALL.get());
1047
1048            parser.addGlobalArgument(arg);
1049            parser.setUsageGroupArgument(arg, allSubCommands);
1050        }
1051    }
1052
1053    /**
1054     * Parses the provided command-line arguments and makes the appropriate changes to the Directory Server
1055     * configuration.
1056     *
1057     * @param args
1058     *            The command-line arguments provided to this program.
1059     * @return The exit code from the configuration processing. A nonzero value indicates that there was some kind of
1060     *         problem during the configuration processing.
1061     */
1062    private int run(String[] args) {
1063        // Register global arguments and sub-commands.
1064        try {
1065            initializeGlobalArguments();
1066            initializeSubCommands();
1067        } catch (ArgumentException e) {
1068            errPrintln(ERR_CANNOT_INITIALIZE_ARGS.get(e.getMessage()));
1069            return ReturnCode.ERROR_USER_DATA.get();
1070        }
1071
1072        ConnectionFactoryProvider cfp = null;
1073        try {
1074            cfp = new ConnectionFactoryProvider(parser, this, CliConstants.DEFAULT_ROOT_USER_DN,
1075                    CliConstants.DEFAULT_ADMINISTRATION_CONNECTOR_PORT, true);
1076            cfp.setIsAnAdminConnection();
1077
1078            // Parse the command-line arguments provided to this program.
1079            parser.parseArguments(args);
1080            checkForConflictingArguments();
1081        } catch (ArgumentException ae) {
1082            parser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
1083            return ReturnCode.CONFLICTING_ARGS.get();
1084        }
1085
1086        // If the usage/version argument was provided, then we don't need
1087        // to do anything else.
1088        if (parser.usageOrVersionDisplayed()) {
1089            return ReturnCode.SUCCESS.get();
1090        }
1091
1092        // Check that we can write on the provided path where we write the
1093        // equivalent non-interactive commands.
1094        if (equivalentCommandFileArgument.isPresent()) {
1095            final String file = equivalentCommandFileArgument.getValue();
1096            if (!canWrite(file)) {
1097                errPrintln(ERR_DSCFG_CANNOT_WRITE_EQUIVALENT_COMMAND_LINE_FILE.get(file));
1098                return ReturnCode.ERROR_UNEXPECTED.get();
1099            } else if (new File(file).isDirectory()) {
1100                errPrintln(ERR_DSCFG_EQUIVALENT_COMMAND_LINE_FILE_DIRECTORY.get(file));
1101                return ReturnCode.ERROR_UNEXPECTED.get();
1102            }
1103        }
1104        // Creates the management context factory which is based on the connection
1105        // provider factory and an authenticated connection factory.
1106        try {
1107            factory = new LDAPManagementContextFactory(cfp);
1108        } catch (ArgumentException e) {
1109            parser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(e.getMessage()));
1110            return ReturnCode.CONFLICTING_ARGS.get();
1111        }
1112
1113        try {
1114            // Handle batch file if any
1115            if (batchArgument.isPresent() || batchFileArgument.isPresent()) {
1116                handleBatch(args);
1117                return ReturnCode.SUCCESS.get();
1118            }
1119
1120            hasSubCommand = parser.getSubCommand() != null;
1121            if (hasSubCommand) {
1122                // Retrieve the sub-command implementation and run it.
1123                return runSubCommand(handlers.get(parser.getSubCommand()));
1124            } else if (isInteractive()) {
1125                // Top-level interactive mode.
1126                return runInteractiveMode();
1127            } else {
1128                parser.displayMessageAndUsageReference(
1129                    getErrStream(), ERR_ERROR_PARSING_ARGS.get(ERR_DSCFG_ERROR_MISSING_SUBCOMMAND.get()));
1130                return ReturnCode.ERROR_USER_DATA.get();
1131            }
1132        } finally {
1133            factory.close();
1134        }
1135    }
1136
1137    private void checkForConflictingArguments() throws ArgumentException {
1138        throwIfConflictingArgsSet(quietArgument, verboseArgument);
1139        throwIfConflictingArgsSet(batchArgument, batchFileArgument);
1140
1141        throwIfSetInInteractiveMode(batchFileArgument);
1142        throwIfSetInInteractiveMode(batchArgument);
1143        throwIfSetInInteractiveMode(quietArgument);
1144
1145        throwIfConflictingArgsSet(scriptFriendlyArgument, verboseArgument);
1146        throwIfConflictingArgsSet(noPropertiesFileArgument, propertiesFileArgument);
1147    }
1148
1149    private void throwIfSetInInteractiveMode(Argument arg) throws ArgumentException {
1150        if (arg.isPresent() && !noPromptArgument.isPresent()) {
1151            throw new ArgumentException(ERR_DSCFG_ERROR_QUIET_AND_INTERACTIVE_INCOMPATIBLE.get(
1152                    arg.getLongIdentifier(), noPromptArgument.getLongIdentifier()));
1153        }
1154    }
1155
1156    private void throwIfConflictingArgsSet(Argument arg1, Argument arg2) throws ArgumentException {
1157        if (arg1.isPresent() && arg2.isPresent()) {
1158            throw new ArgumentException(ERR_TOOL_CONFLICTING_ARGS.get(
1159                    arg1.getLongIdentifier(), arg2.getLongIdentifier()));
1160        }
1161    }
1162
1163    /** Run the top-level interactive console. */
1164    private int runInteractiveMode() {
1165        ConsoleApplication app = this;
1166
1167        // Build menu structure.
1168        final Comparator<RelationDefinition<?, ?>> c = new Comparator<RelationDefinition<?, ?>>() {
1169
1170            @Override
1171            public int compare(RelationDefinition<?, ?> rd1, RelationDefinition<?, ?> rd2) {
1172                final String s1 = rd1.getUserFriendlyName().toString();
1173                final String s2 = rd2.getUserFriendlyName().toString();
1174
1175                return s1.compareToIgnoreCase(s2);
1176            }
1177        };
1178
1179        final Set<RelationDefinition<?, ?>> relations = new TreeSet<>(c);
1180
1181        final Map<RelationDefinition<?, ?>, CreateSubCommandHandler<?, ?>> createHandlers = new HashMap<>();
1182        final Map<RelationDefinition<?, ?>, DeleteSubCommandHandler> deleteHandlers = new HashMap<>();
1183        final Map<RelationDefinition<?, ?>, ListSubCommandHandler> listHandlers = new HashMap<>();
1184        final Map<RelationDefinition<?, ?>, GetPropSubCommandHandler> getPropHandlers = new HashMap<>();
1185        final Map<RelationDefinition<?, ?>, SetPropSubCommandHandler> setPropHandlers = new HashMap<>();
1186
1187        for (final CreateSubCommandHandler<?, ?> ch : handlerFactory.getCreateSubCommandHandlers()) {
1188            relations.add(ch.getRelationDefinition());
1189            createHandlers.put(ch.getRelationDefinition(), ch);
1190        }
1191
1192        for (final DeleteSubCommandHandler dh : handlerFactory.getDeleteSubCommandHandlers()) {
1193            relations.add(dh.getRelationDefinition());
1194            deleteHandlers.put(dh.getRelationDefinition(), dh);
1195        }
1196
1197        for (final ListSubCommandHandler lh : handlerFactory.getListSubCommandHandlers()) {
1198            relations.add(lh.getRelationDefinition());
1199            listHandlers.put(lh.getRelationDefinition(), lh);
1200        }
1201
1202        for (final GetPropSubCommandHandler gh : handlerFactory.getGetPropSubCommandHandlers()) {
1203            relations.add(gh.getRelationDefinition());
1204            getPropHandlers.put(gh.getRelationDefinition(), gh);
1205        }
1206
1207        for (final SetPropSubCommandHandler sh : handlerFactory.getSetPropSubCommandHandlers()) {
1208            relations.add(sh.getRelationDefinition());
1209            setPropHandlers.put(sh.getRelationDefinition(), sh);
1210        }
1211
1212        // Main menu.
1213        final MenuBuilder<Integer> builder = new MenuBuilder<>(app);
1214
1215        builder.setTitle(INFO_DSCFG_HEADING_MAIN_MENU_TITLE.get());
1216        builder.setPrompt(INFO_DSCFG_HEADING_MAIN_MENU_PROMPT.get());
1217        builder.setMultipleColumnThreshold(0);
1218
1219        for (final RelationDefinition<?, ?> rd : relations) {
1220            final MenuCallback<Integer> callback = new SubMenuCallback(app, rd, createHandlers.get(rd),
1221                    deleteHandlers.get(rd), listHandlers.get(rd), setPropHandlers.get(rd));
1222            builder.addNumberedOption(rd.getUserFriendlyName(), callback);
1223        }
1224
1225        builder.addQuitOption();
1226
1227        final Menu<Integer> menu = builder.toMenu();
1228
1229        try {
1230            // Force retrieval of management context.
1231            factory.getManagementContext(app);
1232        } catch (ArgumentException e) {
1233            parser.displayMessageAndUsageReference(getErrStream(), e.getMessageObject());
1234            return ReturnCode.ERROR_USER_DATA.get();
1235        } catch (ClientException e) {
1236            app.errPrintln(e.getMessageObject());
1237            return ReturnCode.ERROR_UNEXPECTED.get();
1238        }
1239
1240        try {
1241            app.println();
1242            app.println();
1243
1244            final MenuResult<Integer> result = menu.run();
1245            if (result.isQuit()) {
1246                return ReturnCode.SUCCESS.get();
1247            } else {
1248                return result.getValue();
1249            }
1250        } catch (ClientException e) {
1251            app.errPrintln(e.getMessageObject());
1252            return ReturnCode.ERROR_UNEXPECTED.get();
1253        }
1254    }
1255
1256    /** Run the provided sub-command handler. */
1257    private int runSubCommand(SubCommandHandler handler) {
1258        try {
1259            final MenuResult<Integer> result = handler.run(this, factory);
1260            if (result.isSuccess()) {
1261                if (isInteractive() && handler.isCommandBuilderUseful()) {
1262                    printCommandBuilder(getCommandBuilder(handler));
1263                }
1264                return result.getValue();
1265            } else {
1266                // User must have quit.
1267                return ReturnCode.ERROR_UNEXPECTED.get();
1268            }
1269        } catch (ArgumentException e) {
1270            errPrintln(e.getMessageObject());
1271            return ReturnCode.ERROR_UNEXPECTED.get();
1272        } catch (ClientException e) {
1273            Throwable cause = e.getCause();
1274            errPrintln();
1275            if (cause instanceof ManagedObjectDecodingException) {
1276                displayManagedObjectDecodingException(this, (ManagedObjectDecodingException) cause);
1277            } else if (cause instanceof MissingMandatoryPropertiesException) {
1278                displayMissingMandatoryPropertyException(this, (MissingMandatoryPropertiesException) cause);
1279            } else if (cause instanceof OperationRejectedException) {
1280                displayOperationRejectedException(this, (OperationRejectedException) cause);
1281            } else {
1282                // Just display the default message.
1283                errPrintln(e.getMessageObject());
1284            }
1285            errPrintln();
1286
1287            return ReturnCode.ERROR_UNEXPECTED.get();
1288        } catch (Exception e) {
1289            errPrintln(LocalizableMessage.raw(stackTraceToSingleLineString(e, true)));
1290            return ReturnCode.ERROR_UNEXPECTED.get();
1291        }
1292    }
1293
1294    /**
1295     * Updates the command builder with the global options: script friendly, verbose, etc. for a given sub command. It
1296     * also adds systematically the no-prompt option.
1297     *
1298     * @param subCommand
1299     *            The sub command handler or common.
1300     * @return <T> The builded command.
1301     */
1302    CommandBuilder getCommandBuilder(final Object subCommand) {
1303        final String commandName = getScriptName();
1304        final SubCommandHandler handler;
1305        final String subCommandName;
1306        if (subCommand instanceof SubCommandHandler) {
1307            handler = (SubCommandHandler) subCommand;
1308            subCommandName = handler.getSubCommand().getName();
1309        } else {
1310            handler = null;
1311            subCommandName = (String) subCommand;
1312        }
1313
1314        final CommandBuilder commandBuilder = new CommandBuilder(commandName, subCommandName);
1315        if (handler != null) {
1316            commandBuilder.append(handler.getCommandBuilder());
1317        }
1318        if (factory != null && factory.getContextCommandBuilder() != null) {
1319            commandBuilder.append(factory.getContextCommandBuilder());
1320        }
1321        if (verboseArgument.isPresent()) {
1322            commandBuilder.addArgument(verboseArgument);
1323        }
1324        if (scriptFriendlyArgument.isPresent()) {
1325            commandBuilder.addArgument(scriptFriendlyArgument);
1326        }
1327
1328        commandBuilder.addArgument(noPromptArgument);
1329
1330        if (propertiesFileArgument.isPresent()) {
1331            commandBuilder.addArgument(propertiesFileArgument);
1332        }
1333        if (noPropertiesFileArgument.isPresent()) {
1334            commandBuilder.addArgument(noPropertiesFileArgument);
1335        }
1336
1337        return commandBuilder;
1338    }
1339
1340    private String getScriptName() {
1341        final String commandName = System.getProperty(PROPERTY_SCRIPT_NAME);
1342        if (commandName != null && commandName.length() != 0) {
1343            return commandName;
1344        }
1345        return DSCONFIGTOOLNAME;
1346    }
1347
1348    /**
1349     * Prints the contents of a command builder. This method has been created since SetPropSubCommandHandler calls it.
1350     * All the logic of DSConfig is on this method. It writes the content of the CommandBuilder to the standard output,
1351     * or to a file depending on the options provided by the user.
1352     *
1353     * @param commandBuilder
1354     *            the command builder to be printed.
1355     */
1356    void printCommandBuilder(CommandBuilder commandBuilder) {
1357        if (displayEquivalentArgument.isPresent()) {
1358            println();
1359            // We assume that the app we are running is this one.
1360            println(INFO_DSCFG_NON_INTERACTIVE.get(commandBuilder));
1361        }
1362        if (equivalentCommandFileArgument.isPresent()) {
1363            String file = equivalentCommandFileArgument.getValue();
1364            BufferedWriter writer = null;
1365            try {
1366                writer = new BufferedWriter(new FileWriter(file, true));
1367
1368                if (!sessionStartTimePrinted) {
1369                    writer.write(SHELL_COMMENT_SEPARATOR + getSessionStartTimeMessage());
1370                    writer.newLine();
1371                    sessionStartTimePrinted = true;
1372                }
1373
1374                sessionEquivalentOperationNumber++;
1375                writer.newLine();
1376                writer.write(SHELL_COMMENT_SEPARATOR
1377                        + INFO_DSCFG_EQUIVALENT_COMMAND_LINE_SESSION_OPERATION_NUMBER
1378                                .get(sessionEquivalentOperationNumber));
1379                writer.newLine();
1380
1381                writer.write(SHELL_COMMENT_SEPARATOR + getCurrentOperationDateMessage());
1382                writer.newLine();
1383
1384                writer.write(commandBuilder.toString());
1385                writer.newLine();
1386                writer.newLine();
1387
1388                writer.flush();
1389            } catch (IOException ioe) {
1390                errPrintln(ERR_DSCFG_ERROR_WRITING_EQUIVALENT_COMMAND_LINE.get(file, ioe));
1391            } finally {
1392                closeSilently(writer);
1393            }
1394        }
1395    }
1396
1397    /**
1398     * Returns the message to be displayed in the file with the equivalent command-line with information about when the
1399     * session started.
1400     *
1401     * @return the message to be displayed in the file with the equivalent command-line with information about when the
1402     *         session started.
1403     */
1404    private String getSessionStartTimeMessage() {
1405        final String date = formatDateTimeStringForEquivalentCommand(new Date(sessionStartTime));
1406        return INFO_DSCFG_SESSION_START_TIME_MESSAGE.get(getScriptName(), date).toString();
1407    }
1408
1409    private void handleBatch(String[] args) {
1410        BufferedReader bReader = null;
1411        try {
1412            if (batchArgument.isPresent()) {
1413                bReader = new BufferedReader(new InputStreamReader(System.in));
1414            } else if (batchFileArgument.isPresent()) {
1415                final String batchFilePath = batchFileArgument.getValue().trim();
1416                bReader = new BufferedReader(new FileReader(batchFilePath));
1417            } else {
1418                throw new IllegalArgumentException("Either --" + OPTION_LONG_BATCH
1419                    + " or --" + OPTION_LONG_BATCH_FILE_PATH + " argument should have been set");
1420            }
1421
1422            List<String> initialArgs = removeBatchArgs(args);
1423
1424            // Split the CLI string into arguments array
1425            String command = "";
1426            String line;
1427            while ((line = bReader.readLine()) != null) {
1428                if ("".equals(line) || line.startsWith("#")) {
1429                    // Empty line or comment
1430                    continue;
1431                }
1432                // command split in several line support
1433                if (line.endsWith("\\")) {
1434                    // command is split into several lines
1435                    command += line.substring(0, line.length() - 1);
1436                    continue;
1437                }
1438
1439                command += line;
1440                command = command.trim();
1441                // string between quotes support
1442                command = replaceSpacesInQuotes(command);
1443                // "\ " support
1444                command = command.replace("\\ ", "##");
1445
1446                String displayCommand = command.replace("\\ ", " ");
1447                println(LocalizableMessage.raw(displayCommand));
1448
1449                // Append initial arguments to the file line
1450                final String[] allArgsArray = buildCommandArgs(initialArgs, command);
1451                int exitCode = main(allArgsArray, getOutputStream(), getErrorStream());
1452                if (exitCode != ReturnCode.SUCCESS.get()) {
1453                    System.exit(filterExitCode(exitCode));
1454                }
1455                println();
1456                // reset command
1457                command = "";
1458            }
1459        } catch (IOException ex) {
1460            errPrintln(ERR_DSCFG_ERROR_READING_BATCH_FILE.get(ex));
1461        } finally {
1462            closeSilently(bReader);
1463        }
1464    }
1465
1466    private String[] buildCommandArgs(List<String> initialArgs, String batchCommand) {
1467        final String[] commandArgs = toCommandArgs(batchCommand);
1468        final int length = commandArgs.length + initialArgs.size();
1469        final List<String> allArguments = new ArrayList<>(length);
1470        Collections.addAll(allArguments, commandArgs);
1471        allArguments.addAll(initialArgs);
1472        return allArguments.toArray(new String[length]);
1473    }
1474
1475    private String[] toCommandArgs(String command) {
1476        String[] fileArguments = command.split("\\s+");
1477        for (int ii = 0; ii < fileArguments.length; ii++) {
1478            fileArguments[ii] = fileArguments[ii].replace("##", " ");
1479        }
1480        return fileArguments;
1481    }
1482
1483    private List<String> removeBatchArgs(String[] args) {
1484        // Build a list of initial arguments,
1485        // removing the batch file option + its value
1486        final List<String> initialArgs = new ArrayList<>();
1487        Collections.addAll(initialArgs, args);
1488        for (Iterator<String> it = initialArgs.iterator(); it.hasNext();) {
1489            final String elem = it.next();
1490            if (batchArgument.isPresent()
1491                    && elem.contains(batchArgument.getLongIdentifier())) {
1492                it.remove();
1493                break;
1494            } else if (batchFileArgument.isPresent()
1495                    && (elem.startsWith("-" + batchFileArgument.getShortIdentifier())
1496                            || elem.contains(batchFileArgument.getLongIdentifier()))) {
1497                // Remove both the batch file arg and its value
1498                it.remove();
1499                it.next();
1500                it.remove();
1501                break;
1502            }
1503        }
1504        return initialArgs;
1505    }
1506
1507    /** Replace spaces in quotes by "\ ". */
1508    private String replaceSpacesInQuotes(final String line) {
1509        StringBuilder newLine = new StringBuilder();
1510        boolean inQuotes = false;
1511        for (int ii = 0; ii < line.length(); ii++) {
1512            char ch = line.charAt(ii);
1513            if (ch == '\"' || ch == '\'') {
1514                inQuotes = !inQuotes;
1515                continue;
1516            }
1517            if (inQuotes && ch == ' ') {
1518                newLine.append("\\ ");
1519            } else {
1520                newLine.append(ch);
1521            }
1522        }
1523        return newLine.toString();
1524    }
1525}