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