001    /*
002     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003     *
004     * Copyright (c) 2008-2010 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    import java.util.regex.*;
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 com.sun.enterprise.universal.i18n.LocalStringsImpl;
052    import static com.sun.enterprise.admin.cli.CLIConstants.EOL;
053    
054    /**
055     * A local list-commands command.
056     *  
057     * @author bnevins
058     * @author Bill Shannon
059     */
060    @Service(name = "list-commands")
061    @Scoped(PerLookup.class)
062    public class ListCommandsCommand extends CLICommand {
063        @Inject
064        private Habitat habitat;
065    
066        private String[] remoteCommands;
067        private String[] localCommands;
068        private List<Pattern> patterns = new ArrayList<Pattern>();
069    
070        @Param(name = "localonly", optional = true)
071        private boolean localOnly;
072    
073        @Param(name = "remoteonly", optional = true)
074        private boolean remoteOnly;
075    
076        @Param(name = "command-pattern", primary = true, optional = true,
077                multiple = true)
078        private List<String> cmds;
079    
080        private static final String SPACES = "                                                            ";
081    
082        private static final LocalStringsImpl strings =
083                new LocalStringsImpl(ListCommandsCommand.class);
084    
085        @Override
086        protected void validate()
087                throws CommandException, CommandValidationException {
088            if (localOnly && remoteOnly) {
089                throw new CommandException(strings.get("listCommands.notBoth"));
090            }
091        }
092    
093        @Override
094        public int executeCommand()
095                throws CommandException, CommandValidationException {
096    
097            // convert the patterns to regular expressions
098            if (cmds != null)
099                for (String pat : cmds)
100                    patterns.add(Pattern.compile(globToRegex(pat)));
101    
102            /*
103             * If we need the remote commands, get them first so that
104             * we prompt for any passwords before printing anything.
105             */
106            if (!localOnly) {
107                try {
108                    remoteCommands = matchCommands(
109                        CLIUtil.getRemoteCommands(habitat, programOpts, env));
110                } catch (CommandException ce) {
111                    /*
112                     * Hide the real cause of the remote failure (almost certainly
113                     * a ConnectException) so that asadmin doesn't try to find the
114                     * closest matching local command (it's "list-commands", duh!).
115                     */
116                    throw new CommandException(ce.getMessage());
117                }
118            }
119    
120            if (!remoteOnly) {
121                localCommands = matchCommands(CLIUtil.getLocalCommands(habitat));
122                printLocalCommands();
123            }
124            if (!localOnly && !remoteOnly)
125                logger.info("");            // a blank line between them
126            if (!localOnly)
127                printRemoteCommands();
128            logger.info("");
129            return 0;
130        }
131    
132        /**
133         * Filter the command list to only those matching the patterns.
134         */
135        private String[] matchCommands(String[] commands) {
136            // filter the commands
137            List<String> matched = new ArrayList<String>();
138            for (String cmd : commands) {
139                if (patterns.size() == 0) {
140                    if (!cmd.startsWith("_"))
141                        matched.add(cmd);
142                } else {
143                    for (Pattern re : patterns)
144                        if (re.matcher(cmd).find())
145                            if (!cmd.startsWith("_") ||
146                                    re.pattern().startsWith("_"))
147                                matched.add(cmd);
148                }
149            }
150    
151            return matched.toArray(new String[matched.size()]);
152        }
153    
154        /**
155         * Convert a shell style glob regular expression to a
156         * Java regular expression.
157         * Code from: http://stackoverflow.com/questions/1247772
158         */
159        private String globToRegex(String line) {
160            line = line.trim();
161            int strLen = line.length();
162            StringBuilder sb = new StringBuilder(strLen);
163            // Remove beginning and ending * globs because they're useless
164            if (line.startsWith("*")) {
165                line = line.substring(1);
166                strLen--;
167            }
168            if (line.endsWith("*")) {
169                line = line.substring(0, strLen-1);
170                strLen--;
171            }
172            boolean escaping = false;
173            int inCurlies = 0;
174            for (char currentChar : line.toCharArray()) {
175                switch (currentChar) {
176                case '*':
177                    if (escaping)
178                        sb.append("\\*");
179                    else
180                        sb.append(".*");
181                    escaping = false;
182                    break;
183                case '?':
184                    if (escaping)
185                        sb.append("\\?");
186                    else
187                        sb.append('.');
188                    escaping = false;
189                    break;
190                case '.':
191                case '(':
192                case ')':
193                case '+':
194                case '|':
195                case '^':
196                case '$':
197                case '@':
198                case '%':
199                    sb.append('\\');
200                    sb.append(currentChar);
201                    escaping = false;
202                    break;
203                case '\\':
204                    if (escaping) {
205                        sb.append("\\\\");
206                        escaping = false;
207                    } else
208                        escaping = true;
209                    break;
210                case '{':
211                    if (escaping) {
212                        sb.append("\\{");
213                    } else {
214                        sb.append('(');
215                        inCurlies++;
216                    }
217                    escaping = false;
218                    break;
219                case '}':
220                    if (inCurlies > 0 && !escaping) {
221                        sb.append(')');
222                        inCurlies--;
223                    } else if (escaping)
224                        sb.append("\\}");
225                    else
226                        sb.append("}");
227                    escaping = false;
228                    break;
229                case ',':
230                    if (inCurlies > 0 && !escaping)
231                        sb.append('|');
232                    else if (escaping)
233                        sb.append("\\,");
234                    else
235                        sb.append(",");
236                    break;
237                default:
238                    escaping = false;
239                    sb.append(currentChar);
240                }
241            }
242            return sb.toString();
243        }
244    
245    
246        void printLocalCommands() {
247            if (localCommands.length == 0) {
248                logger.info(
249                                strings.get("listCommands.localCommandNoMatch"));
250                return;
251            }
252            logger.info(strings.get("listCommands.localCommandHeader"));
253    
254            for (String s : localCommands) {
255                logger.info(s);
256            }
257        }
258    
259        void printRemoteCommands() {
260            if (remoteCommands.length == 0) {
261                logger.info(
262                                strings.get("listCommands.remoteCommandNoMatch"));
263                return;
264            }
265    
266            logger.info(strings.get("listCommands.remoteCommandHeader"));
267            
268            // there are a LOT of remote commands -- make 2 columns
269            int num = remoteCommands.length;
270            int offset = (num / 2) + (num % 2);
271            StringBuilder sb = new StringBuilder();
272     
273            for (int i = 0; i < offset; i++) {
274                sb.append(remoteCommands[i]);
275                sb.append(justify(remoteCommands[i], 40));
276                if (i + offset < num) {
277                    sb.append(remoteCommands[i + offset]);
278                }
279                if (i < offset - 1)
280                    sb.append(EOL);
281            }
282            logger.info(sb.toString());
283        }
284     
285        private String justify(String s, int width) {
286            int numSpaces = width - s.length();
287    
288            if (numSpaces > 0)
289                return SPACES.substring(0, numSpaces);
290            else
291                // the command-name is HUGE.  The formatting will be funky now but
292                // truncating it is a bad idea.  Just add a one-space separator.
293                return " "; 
294        }
295    }