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