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