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    package com.sun.enterprise.admin.cli;
041    
042    import com.sun.enterprise.universal.process.ProcessManager;
043    import com.sun.enterprise.universal.process.ProcessManagerException;
044    import com.sun.enterprise.universal.process.ProcessUtils;
045    import com.sun.enterprise.util.OS;
046    import com.sun.enterprise.util.io.FileUtils;
047    import java.io.File;
048    import java.io.IOException;
049    import org.jvnet.hk2.annotations.*;
050    import org.jvnet.hk2.component.*;
051    import org.glassfish.api.Param;
052    import org.glassfish.api.admin.*;
053    import com.sun.enterprise.admin.cli.remote.RemoteCommand;
054    import com.sun.enterprise.admin.cli.remote.DASUtils;
055    
056    /**
057     * The stop-domain command.
058     *
059     * @author Byron Nevins
060     * @author Bill Shannon
061     */
062    @Service(name = "stop-domain")
063    @Scoped(PerLookup.class)
064    public class StopDomainCommand extends LocalDomainCommand {
065    
066        @Param(name = "domain_name", primary = true, optional = true)
067        private String userArgDomainName;
068        @Param(name = "force", optional = true, defaultValue = "true")
069        Boolean force;
070        @Param(optional = true, defaultValue = "false")
071        Boolean kill;
072        private boolean local;
073        private static final long WAIT_FOR_DAS_TIME_MS = 60000; // 1 minute
074    
075        @Override
076        protected void validate()
077                throws CommandException {
078            setDomainName(userArgDomainName);
079            super.validate(); // which calls initDomain() !!
080            local = getServerDirs().getServerName() != null;
081        }
082    
083        /**
084         * Override initDomain in LocalDomainCommand to only initialize
085         * the local domain information (name, directory) in the local
086         * case, when no --host has been specified.
087         */
088        @Override
089        protected void initDomain() throws CommandException {
090            // only initialize local domain information if it's a local operation
091            if (programOpts.getHost().equals(CLIConstants.DEFAULT_HOSTNAME))
092                super.initDomain();
093            else if (userArgDomainName != null)   // remote case
094                throw new CommandException(
095                        Strings.get("StopDomain.noDomainNameAllowed"));
096        }
097    
098        @Override
099        protected int executeCommand()
100                throws CommandException {
101    
102            if (local) {
103                // if the local password isn't available, the domain isn't running
104                // (localPassword is set by initDomain)
105                if (getServerDirs().getLocalPassword() == null)
106                    return dasNotRunning(local);
107    
108                programOpts.setHostAndPort(getAdminAddress());
109                logger.finer("Stopping local domain on port "
110                        + programOpts.getPort());
111    
112                /*
113                 * If we're using the local password, we don't want to prompt
114                 * for a new password.  If the local password doesn't work it
115                 * most likely means we're talking to the wrong server.
116                 */
117                programOpts.setInteractive(false);
118    
119                // in the local case, make sure we're talking to the correct DAS
120                if (!isThisDAS(getDomainRootDir()))
121                    return dasNotRunning(local);
122    
123                logger.finer("It's the correct DAS");
124            }
125            else { // remote
126                // Verify that the DAS is running and reachable
127                if (!DASUtils.pingDASQuietly(programOpts, env))
128                    return dasNotRunning(local);
129    
130                logger.finer("DAS is running");
131                programOpts.setInteractive(false);
132            }
133    
134            /*
135             * At this point any options will have been prompted for, and
136             * the password will have been prompted for by pingDASQuietly,
137             * so even if the password is wrong we don't want any more
138             * prompting here.
139             */
140    
141            doCommand();
142            return 0;
143        }
144    
145        /**
146         * Print message and return exit code when
147         * we detect that the DAS is not running.
148         */
149        protected int dasNotRunning(boolean local) throws CommandException {
150            if (kill) {
151                if (local)
152                    return kill();
153                else // remote.  We can NOT kill and we can't ask it to kill itself.
154                    throw new CommandException(Strings.get("StopDomain.dasNotRunningRemotely"));
155            }
156    
157            // by definition this is not an error
158            // https://glassfish.dev.java.net/issues/show_bug.cgi?id=8387
159            if (local)
160                logger.warning(Strings.get("StopDomain.dasNotRunning", getDomainRootDir()));
161            else
162                logger.warning(Strings.get("StopDomain.dasNotRunningRemotely"));
163            return 0;
164        }
165    
166        /**
167         * Execute the actual stop-domain command.
168         */
169        protected void doCommand() throws CommandException {
170            // run the remote stop-domain command and throw away the output
171            RemoteCommand cmd = new RemoteCommand(getName(), programOpts, env);
172            cmd.executeAndReturnOutput("stop-domain", "--force", force.toString());
173            waitForDeath();
174    
175            if (kill && local) {
176                kill();
177            }
178        }
179    
180        /**
181         * Wait for the server to die.
182         */
183        protected void waitForDeath() throws CommandException {
184            if (!programOpts.isTerse()) {
185                // use stdout because logger always appends a newline
186                System.out.print(Strings.get("StopDomain.WaitDASDeath") + " ");
187            }
188            long startWait = System.currentTimeMillis();
189            boolean alive = true;
190            int count = 0;
191    
192            while (!timedOut(startWait)) {
193                if (!isRunning()) {
194                    alive = false;
195                    break;
196                }
197                try {
198                    Thread.sleep(100);
199                    if (!programOpts.isTerse() && count++ % 10 == 0)
200                        System.out.print(".");
201                }
202                catch (InterruptedException ex) {
203                    // don't care
204                }
205            }
206    
207            if (!programOpts.isTerse())
208                System.out.println();
209    
210            if (alive) {
211                throw new CommandException(Strings.get("StopDomain.DASNotDead",
212                        (WAIT_FOR_DAS_TIME_MS / 1000)));
213            }
214        }
215    
216        private boolean timedOut(long startTime) {
217            return (System.currentTimeMillis() - startTime) > WAIT_FOR_DAS_TIME_MS;
218        }
219    
220        private int kill() throws CommandException {
221            File prevPid = null;
222            String pids = null;
223    
224            try {
225                prevPid = new File(getServerDirs().getPidFile().getPath() + ".prev");
226    
227                if (!prevPid.canRead())
228                    throw new CommandException(Strings.get("StopDomain.nopidprev", prevPid));
229    
230                pids = FileUtils.readSmallFile(prevPid).trim();
231                String s = ProcessUtils.kill(Integer.parseInt(pids));
232                
233                if(s != null)
234                    logger.finer(s);
235            }
236            catch (CommandException ce) {
237                throw ce;
238            }
239            catch (Exception ex) {
240                throw new CommandException(Strings.get("StopDomain.pidprevreaderror",
241                        prevPid, ex.getMessage()));
242            }
243            return 0;
244        }
245    }