001    /*
002     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003     *
004     * Copyright (c) 1997-2011 Oracle and/or its affiliates. All rights reserved.
005     *
006     * The contents of this file are subject to the terms of either the GNU
007     * General Public License Version 2 only ("GPL") or the Common Development
008     * and Distribution License("CDDL") (collectively, the "License").  You
009     * may not use this file except in compliance with the License.  You can
010     * obtain a copy of the License at
011     * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
012     * or packager/legal/LICENSE.txt.  See the License for the specific
013     * language governing permissions and limitations under the License.
014     *
015     * When distributing the software, include this License Header Notice in each
016     * file and include the License file at packager/legal/LICENSE.txt.
017     *
018     * GPL Classpath Exception:
019     * Oracle designates this particular file as subject to the "Classpath"
020     * exception as provided by Oracle in the GPL Version 2 section of the License
021     * file that accompanied this code.
022     *
023     * Modifications:
024     * If applicable, add the following below the License Header, with the fields
025     * enclosed by brackets [] replaced by your own identifying information:
026     * "Portions Copyright [year] [name of copyright owner]"
027     *
028     * Contributor(s):
029     * If you wish your version of this file to be governed by only the CDDL or
030     * only the GPL Version 2, indicate your decision by adding "[Contributor]
031     * elects to include this software in this distribution under the [CDDL or GPL
032     * Version 2] license."  If you don't indicate a single choice of license, a
033     * recipient has the option to distribute your version of this file under
034     * either the CDDL, the GPL Version 2 or to extend the choice of license to
035     * its licensees as provided above.  However, if you add GPL Version 2 code
036     * and therefore, elected the GPL Version 2 license, then the option applies
037     * only if the new code is made subject to such option by the copyright
038     * holder.
039     */
040    
041    package com.sun.enterprise.admin.cli;
042    
043    import java.io.*;
044    import java.util.*;
045    import java.util.logging.*;
046    
047    import org.jvnet.hk2.annotations.*;
048    import org.jvnet.hk2.component.*;
049    import org.glassfish.api.Param;
050    import org.glassfish.api.admin.*;
051    import static com.sun.enterprise.admin.cli.CLIConstants.*;
052    import com.sun.enterprise.admin.launcher.GFLauncher;
053    import com.sun.enterprise.admin.launcher.GFLauncherException;
054    import com.sun.enterprise.admin.launcher.GFLauncherFactory;
055    import com.sun.enterprise.admin.launcher.GFLauncherInfo;
056    import com.sun.enterprise.admin.util.CommandModelData.ParamModelData;
057    import com.sun.enterprise.universal.i18n.LocalStringsImpl;
058    import com.sun.enterprise.universal.process.ProcessStreamDrainer;
059    import com.sun.enterprise.universal.xml.MiniXmlParserException;
060    import org.glassfish.security.common.FileRealmHelper;
061    
062    /**
063     * The start-domain command.
064     *
065     * @author bnevins
066     * @author Bill Shannon
067     */
068    @Service(name = "start-domain")
069    @Scoped(PerLookup.class)
070    public class StartDomainCommand extends LocalDomainCommand implements StartServerCommand {
071    
072        private GFLauncherInfo info;
073        private GFLauncher launcher;
074        @Param(optional = true, shortName = "v", defaultValue = "false")
075        private boolean verbose;
076        @Param(optional = true, defaultValue = "false")
077        private boolean upgrade;
078        @Param(optional = true, shortName = "d", defaultValue = "false")
079        private boolean debug;
080        @Param(name = "domain_name", primary = true, optional = true)
081        private String domainName0;
082        @Param(name = "_dry-run", shortName = "n", optional = true,
083                defaultValue = "false")
084        private boolean dry_run;
085        private static final LocalStringsImpl strings =
086                new LocalStringsImpl(StartDomainCommand.class);
087        // the name of the master password option
088        private StartServerHelper helper;
089        private static final String newpwName = Environment.AS_ADMIN_ENV_PREFIX + "NEWPASSWORD";
090    
091        @Override
092        public RuntimeType getType() {
093            return RuntimeType.DAS;
094        }
095    
096        @Override
097        protected void validate()
098                throws CommandException, CommandValidationException {
099            setDomainName(domainName0);
100            super.validate();
101        }
102    
103        @Override
104        protected int executeCommand() throws CommandException {
105            try {
106                // createLauncher needs to go before the helper is created!!
107                createLauncher();
108                final String mpv = getMasterPassword();
109    
110                helper = new StartServerHelper(
111                        logger,
112                        programOpts.isTerse(),
113                        getServerDirs(),
114                        launcher,
115                        mpv,
116                        debug);
117    
118                if (helper.prepareForLaunch() == false)
119                    return ERROR;
120    
121                if (!upgrade && launcher.needsManualUpgrade()) {
122                    logger.info(strings.get("manualUpgradeNeeded"));
123                    return ERROR;
124                }
125                doAutoUpgrade(mpv);
126    
127                if (dry_run) {
128                    logger.fine(Strings.get("dry_run_msg"));
129                    List<String> cmd = launcher.getCommandLine();
130                    StringBuilder sb = new StringBuilder();
131                    for (String s : cmd) {
132                        sb.append(s);
133                        sb.append('\n');
134                    }
135                    logger.info(sb.toString());
136                    return SUCCESS;
137                }
138    
139                doAdminPasswordCheck();
140                
141                // launch returns very quickly if verbose is not set
142                // if verbose is set then it returns after the domain dies
143                launcher.launch();
144    
145                if (verbose || upgrade) { // we can potentially loop forever here...
146                    while (true) {
147                        int returnValue = launcher.getExitValue();
148    
149                        switch (returnValue) {
150                            case RESTART_NORMAL:
151                                logger.info(strings.get("restart"));
152                                break;
153                            case RESTART_DEBUG_ON:
154                                logger.info(strings.get("restartChangeDebug", "on"));
155                                info.setDebug(true);
156                                break;
157                            case RESTART_DEBUG_OFF:
158                                logger.info(strings.get("restartChangeDebug", "off"));
159                                info.setDebug(false);
160                                break;
161                            default:
162                                return returnValue;
163                        }
164    
165                        if (CLIConstants.debugMode)
166                            System.setProperty(CLIConstants.WALL_CLOCK_START_PROP,
167                                    "" + System.currentTimeMillis());
168    
169                        launcher.relaunch();
170                    }
171    
172                }
173                else {
174                    helper.waitForServer();
175                    helper.report();
176                    return SUCCESS;
177                }
178            }
179            catch (GFLauncherException gfle) {
180                throw new CommandException(gfle.getMessage());
181            }
182            catch (MiniXmlParserException me) {
183                throw new CommandException(me);
184            }
185        }
186    
187        /**
188         * Create a launcher for the domain specified by arguments to
189         * this command.  The launcher is for a server of the specified type.
190         * Sets the launcher and info fields.
191         * It has to be public because it is part of an interface
192         */
193        @Override
194        public void createLauncher()
195                throws GFLauncherException, MiniXmlParserException {
196            launcher = GFLauncherFactory.getInstance(getType());
197            info = launcher.getInfo();
198    
199            info.setDomainName(getDomainName());
200            info.setDomainParentDir(getDomainsDir().getPath());
201            info.setVerbose(verbose || upgrade);
202            info.setDebug(debug);
203            info.setUpgrade(upgrade);
204    
205            info.setRespawnInfo(programOpts.getClassName(),
206                    programOpts.getClassPath(),
207                    respawnArgs());
208    
209            launcher.setup();
210        }
211    
212        /**
213         * Return the asadmin command line arguments necessar to start
214         * this domain admin server.
215         */
216        private String[] respawnArgs() {
217            List<String> args = new ArrayList<String>(15);
218            args.addAll(Arrays.asList(programOpts.getProgramArguments()));
219    
220            // now the start-domain specific arguments
221            args.add(getName());    // the command name
222            args.add("--verbose=" + String.valueOf(verbose));
223            args.add("--debug=" + String.valueOf(debug));
224            args.add("--domaindir");
225            args.add(getDomainsDir().toString());
226            if (ok(getDomainName()))
227                args.add(getDomainName());  // the operand
228    
229            if (logger.isLoggable(Level.FINER))
230                logger.log(Level.FINER, "Respawn args: {0}", args.toString());
231            String[] a = new String[args.size()];
232            args.toArray(a);
233            return a;
234        }
235    
236        /*
237         * This is useful for debugging restart-domain problems.
238         * In that case the Server process will run this class and it is fairly
239         * involved to attach a debugger (though not bad -- see RestartDomain on
240         * the server to see how).  Standard output disappears.  This is a
241         * generally useful method.  Feel free to copy & paste!
242         */
243        private void debug(String s) {
244            try {
245                PrintStream ps = new PrintStream(
246                        new FileOutputStream("startdomain.txt", true));
247                ps.println(new Date().toString() + ":  " + s);
248            }
249            catch (FileNotFoundException ex) {
250                //
251            }
252        }
253    
254        /*
255         * If this domain needs to be upgraded and --upgrade wasn't
256         * specified, first start the domain to do the upgrade and
257         * then start the domain again for real.
258         */
259        private void doAutoUpgrade(String mpv) throws GFLauncherException, MiniXmlParserException, CommandException {
260            if (upgrade || !launcher.needsAutoUpgrade())
261                return;
262    
263            logger.info(strings.get("upgradeNeeded"));
264            info.setUpgrade(true);
265            launcher.setup();
266            launcher.launch();
267            Process p = launcher.getProcess();
268            int exitCode = -1;
269            try {
270                exitCode = p.waitFor();
271            }
272            catch (InterruptedException ex) {
273                // should never happen
274            }
275            if (exitCode != SUCCESS) {
276                ProcessStreamDrainer psd =
277                        launcher.getProcessStreamDrainer();
278                String output = psd.getOutErrString();
279                if (ok(output))
280                    throw new CommandException(
281                            strings.get("upgradeFailedOutput",
282                            info.getDomainName(), exitCode, output));
283                else
284                    throw new CommandException(strings.get("upgradeFailed",
285                            info.getDomainName(), exitCode));
286            }
287            logger.info(strings.get("upgradeSuccessful"));
288    
289            // need a new launcher to start the domain for real
290            createLauncher();
291            // continue with normal start...
292        }
293        
294        /*
295         * Check to make sure that at least one admin user is able to login.
296         * If none is found, then prompt for an admin password.
297         * 
298         * NOTE: this depends on launcher.setup having already been called.
299         */
300        private void doAdminPasswordCheck() throws CommandException {
301            String arfile = launcher.getAdminRealmKeyFile();
302            if (arfile != null) {  
303                try {
304                    FileRealmHelper ar = new FileRealmHelper(arfile);
305                    if (!ar.hasAuthenticatableUser()) {
306                        // Prompt for the password for the first user and set it
307                        Set<String> names = ar.getUserNames();
308                        if (names == null || names.isEmpty()) {
309                            throw new CommandException("no admin users");
310                        }
311                        String auser = names.iterator().next();
312                        ParamModelData npwo = new ParamModelData(newpwName, String.class, false, null);
313                        npwo.description = strings.get("new.adminpw", auser);
314                        npwo.param._password = true;
315                        logger.info(strings.get("new.adminpw.prompt"));
316                        String npw = super.getPassword(npwo, null, true);
317                        if (npw == null) {
318                            throw new CommandException(strings.get("no.console"));
319                        }
320                        ar.updateUser(auser, auser, npw.toCharArray(), null);
321                        ar.persist();
322                    }
323                } catch (IOException ioe) {
324                    throw new CommandException(ioe);
325                }
326            }
327        }
328    }