001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.activemq.console.command;
018    
019    import org.w3c.dom.Attr;
020    import org.w3c.dom.Element;
021    import org.xml.sax.SAXException;
022    
023    import javax.xml.parsers.DocumentBuilder;
024    import javax.xml.parsers.DocumentBuilderFactory;
025    import javax.xml.parsers.ParserConfigurationException;
026    import javax.xml.transform.*;
027    import javax.xml.transform.dom.DOMSource;
028    import javax.xml.transform.stream.StreamResult;
029    import javax.xml.xpath.XPath;
030    import javax.xml.xpath.XPathConstants;
031    import javax.xml.xpath.XPathExpressionException;
032    import javax.xml.xpath.XPathFactory;
033    import java.io.*;
034    import java.nio.ByteBuffer;
035    import java.nio.channels.FileChannel;
036    import java.util.List;
037    
038    public class CreateCommand extends AbstractCommand {
039    
040        protected final String[] helpFile = new String[] {
041            "Task Usage: Main create path/to/brokerA [create-options]",
042            "Description:  Creates a runnable broker instance in the specified path.",
043            "",
044            "List Options:",
045            "    --amqconf <file path>   Path to ActiveMQ conf file that will be used in the broker instance. Default is: conf/activemq.xml",
046            "    --version               Display the version information.",
047            "    -h,-?,--help            Display the create broker help information.",
048            ""
049        };
050    
051        protected final String DEFAULT_TARGET_ACTIVEMQ_CONF = "conf/activemq.xml"; // default activemq conf to create in the new broker instance
052        protected final String DEFAULT_BROKERNAME_XPATH = "/beans/broker/@brokerName"; // default broker name xpath to change the broker name
053    
054        protected final String[] BASE_SUB_DIRS = { "bin", "conf" }; // default sub directories that will be created
055        protected final String BROKER_NAME_REGEX = "[$][{]brokerName[}]"; // use to replace broker name property holders
056    
057        protected String amqConf = "conf/activemq.xml"; // default conf if no conf is specified via --amqconf
058    
059        // default files to create 
060        protected String[][] fileWriteMap = {
061            { "winActivemq", "bin/${brokerName}.bat" },
062            { "unixActivemq", "bin/${brokerName}" }
063        };
064    
065    
066        protected String brokerName;
067        protected File amqHome;
068        protected File targetAmqBase;
069    
070        @Override
071        public String getName() {
072            return "create";
073        }
074    
075        @Override
076        public String getOneLineDescription() {
077            return "Creates a runnable broker instance in the specified path.";
078        }
079    
080        protected void runTask(List<String> tokens) throws Exception {
081            context.print("Running create broker task...");
082            amqHome = new File(System.getProperty("activemq.home"));
083            for (String token : tokens) {
084    
085                targetAmqBase = new File(token);
086                brokerName = targetAmqBase.getName();
087                
088    
089                if (targetAmqBase.exists()) {
090                    BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
091                    String resp;
092                    while (true) {
093                        context.print("Target directory (" + targetAmqBase.getCanonicalPath() + ") already exists. Overwrite (y/n): ");
094                        resp = console.readLine();
095                        if (resp.equalsIgnoreCase("y") || resp.equalsIgnoreCase("yes")) {
096                            break;
097                        } else if (resp.equalsIgnoreCase("n") || resp.equalsIgnoreCase("no")) {
098                            return;
099                        }
100                    }
101                }
102    
103                context.print("Creating directory: " + targetAmqBase.getCanonicalPath());
104                targetAmqBase.mkdirs();
105                createSubDirs(targetAmqBase, BASE_SUB_DIRS);
106                writeFileMapping(targetAmqBase, fileWriteMap);
107                copyActivemqConf(amqHome, targetAmqBase, amqConf);
108                copyConfDirectory(new File(amqHome, "conf"), new File(targetAmqBase, "conf"));
109            }
110        }
111    
112        /**
113         * Handle the --amqconf options.
114         *
115         * @param token  - option token to handle
116         * @param tokens - succeeding command arguments
117         * @throws Exception
118         */
119        protected void handleOption(String token, List<String> tokens) throws Exception {
120            if (token.startsWith("--amqconf")) {
121                // If no amqconf specified, or next token is a new option
122                if (tokens.isEmpty() || tokens.get(0).startsWith("-")) {
123                    context.printException(new IllegalArgumentException("Attributes to amqconf not specified"));
124                    return;
125                }
126    
127                amqConf = tokens.remove(0);
128            } else {
129                // Let super class handle unknown option
130                super.handleOption(token, tokens);
131            }
132        }
133    
134        protected void createSubDirs(File target, String[] subDirs) throws IOException {
135            File subDirFile;
136            for (String subDir : BASE_SUB_DIRS) {
137                subDirFile = new File(target, subDir);
138                context.print("Creating directory: " + subDirFile.getCanonicalPath());
139                subDirFile.mkdirs();
140            }
141        }
142    
143        protected void writeFileMapping(File targetBase, String[][] fileWriteMapping) throws IOException {
144            for (String[] fileWrite : fileWriteMapping) {
145                File dest = new File(targetBase, resolveParam(BROKER_NAME_REGEX, brokerName, fileWrite[1]));
146                context.print("Creating new file: " + dest.getCanonicalPath());
147                writeFile(fileWrite[0], dest);
148            }
149        }
150    
151        protected void copyActivemqConf(File srcBase, File targetBase, String activemqConf) throws IOException, ParserConfigurationException, SAXException, TransformerException, XPathExpressionException {
152            File src = new File(srcBase, activemqConf);
153    
154            if (!src.exists()) {
155                throw new FileNotFoundException("File: " + src.getCanonicalPath() + " not found.");
156            }
157    
158            File dest = new File(targetBase, DEFAULT_TARGET_ACTIVEMQ_CONF);
159            context.print("Copying from: " + src.getCanonicalPath() + "\n          to: " + dest.getCanonicalPath());
160    
161            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
162            Element docElem = builder.parse(src).getDocumentElement();
163    
164            XPath xpath = XPathFactory.newInstance().newXPath();
165            Attr brokerNameAttr = (Attr) xpath.evaluate(DEFAULT_BROKERNAME_XPATH, docElem, XPathConstants.NODE);
166            brokerNameAttr.setValue(brokerName);
167    
168            writeToFile(new DOMSource(docElem), dest);
169        }
170    
171        protected void printHelp() {
172            context.printHelp(helpFile);
173        }
174    
175        // write the default files to create (i.e. script files)
176        private void writeFile(String typeName, File dest) throws IOException {
177            String data;
178            if (typeName.equals("winActivemq")) {
179                data = winActivemqData;
180                data = resolveParam("[$][{]activemq.home[}]", amqHome.getCanonicalPath().replaceAll("[\\\\]", "/"), data);
181                data = resolveParam("[$][{]activemq.base[}]", targetAmqBase.getCanonicalPath().replaceAll("[\\\\]", "/"), data);
182            } else if (typeName.equals("unixActivemq")) {
183                data = getUnixActivemqData();
184                data = resolveParam("[$][{]activemq.home[}]", amqHome.getCanonicalPath().replaceAll("[\\\\]", "/"), data);
185                data = resolveParam("[$][{]activemq.base[}]", targetAmqBase.getCanonicalPath().replaceAll("[\\\\]", "/"), data);
186            } else {
187                throw new IllegalStateException("Unknown file type: " + typeName);
188            }
189    
190            ByteBuffer buf = ByteBuffer.allocate(data.length());
191            buf.put(data.getBytes());
192            buf.flip();
193    
194            FileChannel destinationChannel = new FileOutputStream(dest).getChannel();
195            destinationChannel.write(buf);
196            destinationChannel.close();
197    
198            // Set file permissions available for Java 6.0 only
199            dest.setExecutable(true);
200            dest.setReadable(true);
201            dest.setWritable(true);
202        }
203    
204        // utlity method to write an xml source to file
205        private void writeToFile(Source src, File file) throws TransformerException {
206            TransformerFactory tFactory = TransformerFactory.newInstance();
207            Transformer fileTransformer = tFactory.newTransformer();
208    
209            Result res = new StreamResult(file);
210            fileTransformer.transform(src, res);
211        }
212    
213        // utility method to copy one file to another
214        private void copyFile(File from, File dest) throws IOException {
215            if (!from.exists()) {
216                return;
217            }
218            FileChannel sourceChannel = new FileInputStream(from).getChannel();
219            FileChannel destinationChannel = new FileOutputStream(dest).getChannel();
220            sourceChannel.transferTo(0, sourceChannel.size(), destinationChannel);
221            sourceChannel.close();
222            destinationChannel.close();
223        }
224    
225        private void copyConfDirectory(File from, File dest) throws IOException {
226            if (from.isDirectory()) {
227                String files[] = from.list();
228    
229                for (String file : files) {
230                    File srcFile = new File(from, file);
231                    if (srcFile.isFile() && !srcFile.getName().equals("activemq.xml")) {
232                        File destFile = new File(dest, file);
233                        context.print("Copying from: " + srcFile.getCanonicalPath() + "\n          to: " + destFile.getCanonicalPath());
234                        copyFile(srcFile, destFile);
235                    }
236                }
237            } else {
238                throw new IOException(from + " is not a directory");
239            }
240        }
241    
242        // replace a property place holder (paramName) with the paramValue
243        private String resolveParam(String paramName, String paramValue, String target) {
244            return target.replaceAll(paramName, paramValue);
245        }
246    
247        // Embedded windows script data
248        private static final String winActivemqData =
249            "@echo off\n"
250                + "set ACTIVEMQ_HOME=\"${activemq.home}\"\n"
251                + "set ACTIVEMQ_BASE=\"${activemq.base}\"\n"
252                + "\n"
253                + "set PARAM=%1\n"
254                + ":getParam\n"
255                + "shift\n"
256                + "if \"%1\"==\"\" goto end\n"
257                + "set PARAM=%PARAM% %1\n"
258                + "goto getParam\n"
259                + ":end\n"
260                + "\n"
261                + "%ACTIVEMQ_HOME%/bin/activemq %PARAM%";
262    
263    
264       private String getUnixActivemqData() {
265           StringBuffer res = new StringBuffer();
266           res.append("## Figure out the ACTIVEMQ_BASE from the directory this script was run from\n");
267           res.append("PRG=\"$0\"\n");
268           res.append("progname=`basename \"$0\"`\n");
269           res.append("saveddir=`pwd`\n");
270           res.append("# need this for relative symlinks\n");
271           res.append("dirname_prg=`dirname \"$PRG\"`\n");
272           res.append("cd \"$dirname_prg\"\n");
273           res.append("while [ -h \"$PRG\" ] ; do\n");
274           res.append("  ls=`ls -ld \"$PRG\"`\n");
275           res.append("  link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n");
276           res.append("  if expr \"$link\" : '.*/.*' > /dev/null; then\n");
277           res.append("    PRG=\"$link\"\n");
278           res.append("  else\n");
279           res.append("    PRG=`dirname \"$PRG\"`\"/$link\"\n");
280           res.append("  fi\n");
281           res.append("done\n");
282           res.append("ACTIVEMQ_BASE=`dirname \"$PRG\"`/..\n");
283           res.append("cd \"$saveddir\"\n\n");
284           res.append("ACTIVEMQ_BASE=`cd \"$ACTIVEMQ_BASE\" && pwd`\n\n");
285           res.append("## Add system properties for this instance here (if needed), e.g\n");
286           res.append("#export ACTIVEMQ_OPTS_MEMORY=\"-Xms256M -Xmx1G\"\n");
287           res.append("#export ACTIVEMQ_OPTS=\"$ACTIVEMQ_OPTS_MEMORY -Dorg.apache.activemq.UseDedicatedTaskRunner=true -Djava.util.logging.config.file=logging.properties\"\n\n");
288           res.append("export ACTIVEMQ_HOME=${activemq.home}\n");
289           res.append("export ACTIVEMQ_BASE=$ACTIVEMQ_BASE\n\n");
290           res.append("${ACTIVEMQ_HOME}/bin/activemq \"$*\"");
291           return res.toString();
292       }
293    
294    }