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