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