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 protected void runTask(List<String> tokens) throws Exception {
071 context.print("Running create broker task...");
072 amqHome = new File(System.getProperty("activemq.home"));
073 for (String token : tokens) {
074
075 targetAmqBase = new File(token);
076 brokerName = targetAmqBase.getName();
077
078
079 if (targetAmqBase.exists()) {
080 BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
081 String resp;
082 while (true) {
083 context.print("Target directory (" + targetAmqBase.getCanonicalPath() + ") already exists. Overwrite (y/n): ");
084 resp = console.readLine();
085 if (resp.equalsIgnoreCase("y") || resp.equalsIgnoreCase("yes")) {
086 break;
087 } else if (resp.equalsIgnoreCase("n") || resp.equalsIgnoreCase("no")) {
088 return;
089 }
090 }
091 }
092
093 context.print("Creating directory: " + targetAmqBase.getCanonicalPath());
094 targetAmqBase.mkdirs();
095 createSubDirs(targetAmqBase, BASE_SUB_DIRS);
096 writeFileMapping(targetAmqBase, fileWriteMap);
097 copyActivemqConf(amqHome, targetAmqBase, amqConf);
098 copyConfDirectory(new File(amqHome, "conf"), new File(targetAmqBase, "conf"));
099 }
100 }
101
102 /**
103 * Handle the --amqconf options.
104 *
105 * @param token - option token to handle
106 * @param tokens - succeeding command arguments
107 * @throws Exception
108 */
109 protected void handleOption(String token, List<String> tokens) throws Exception {
110 if (token.startsWith("--amqconf")) {
111 // If no amqconf specified, or next token is a new option
112 if (tokens.isEmpty() || tokens.get(0).startsWith("-")) {
113 context.printException(new IllegalArgumentException("Attributes to amqconf not specified"));
114 return;
115 }
116
117 amqConf = tokens.remove(0);
118 } else {
119 // Let super class handle unknown option
120 super.handleOption(token, tokens);
121 }
122 }
123
124 protected void createSubDirs(File target, String[] subDirs) throws IOException {
125 File subDirFile;
126 for (String subDir : BASE_SUB_DIRS) {
127 subDirFile = new File(target, subDir);
128 context.print("Creating directory: " + subDirFile.getCanonicalPath());
129 subDirFile.mkdirs();
130 }
131 }
132
133 protected void writeFileMapping(File targetBase, String[][] fileWriteMapping) throws IOException {
134 for (String[] fileWrite : fileWriteMapping) {
135 File dest = new File(targetBase, resolveParam(BROKER_NAME_REGEX, brokerName, fileWrite[1]));
136 context.print("Creating new file: " + dest.getCanonicalPath());
137 writeFile(fileWrite[0], dest);
138 }
139 }
140
141 protected void copyActivemqConf(File srcBase, File targetBase, String activemqConf) throws IOException, ParserConfigurationException, SAXException, TransformerException, XPathExpressionException {
142 File src = new File(srcBase, activemqConf);
143
144 if (!src.exists()) {
145 throw new FileNotFoundException("File: " + src.getCanonicalPath() + " not found.");
146 }
147
148 File dest = new File(targetBase, DEFAULT_TARGET_ACTIVEMQ_CONF);
149 context.print("Copying from: " + src.getCanonicalPath() + "\n to: " + dest.getCanonicalPath());
150
151 DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
152 Element docElem = builder.parse(src).getDocumentElement();
153
154 XPath xpath = XPathFactory.newInstance().newXPath();
155 Attr brokerNameAttr = (Attr) xpath.evaluate(DEFAULT_BROKERNAME_XPATH, docElem, XPathConstants.NODE);
156 brokerNameAttr.setValue(brokerName);
157
158 writeToFile(new DOMSource(docElem), dest);
159 }
160
161 protected void printHelp() {
162 context.printHelp(helpFile);
163 }
164
165 // write the default files to create (i.e. script files)
166 private void writeFile(String typeName, File dest) throws IOException {
167 String data;
168 if (typeName.equals("winActivemq")) {
169 data = winActivemqData;
170 data = resolveParam("[$][{]activemq.home[}]", amqHome.getCanonicalPath().replaceAll("[\\\\]", "/"), data);
171 data = resolveParam("[$][{]activemq.base[}]", targetAmqBase.getCanonicalPath().replaceAll("[\\\\]", "/"), data);
172 } else if (typeName.equals("unixActivemq")) {
173 data = getUnixActivemqData();
174 data = resolveParam("[$][{]activemq.home[}]", amqHome.getCanonicalPath().replaceAll("[\\\\]", "/"), data);
175 data = resolveParam("[$][{]activemq.base[}]", targetAmqBase.getCanonicalPath().replaceAll("[\\\\]", "/"), data);
176 } else {
177 throw new IllegalStateException("Unknown file type: " + typeName);
178 }
179
180 ByteBuffer buf = ByteBuffer.allocate(data.length());
181 buf.put(data.getBytes());
182 buf.flip();
183
184 FileChannel destinationChannel = new FileOutputStream(dest).getChannel();
185 destinationChannel.write(buf);
186 destinationChannel.close();
187
188 // Set file permissions available for Java 6.0 only
189 dest.setExecutable(true);
190 dest.setReadable(true);
191 dest.setWritable(true);
192 }
193
194 // utlity method to write an xml source to file
195 private void writeToFile(Source src, File file) throws TransformerException {
196 TransformerFactory tFactory = TransformerFactory.newInstance();
197 Transformer fileTransformer = tFactory.newTransformer();
198
199 Result res = new StreamResult(file);
200 fileTransformer.transform(src, res);
201 }
202
203 // utility method to copy one file to another
204 private void copyFile(File from, File dest) throws IOException {
205 if (!from.exists()) {
206 return;
207 }
208 FileChannel sourceChannel = new FileInputStream(from).getChannel();
209 FileChannel destinationChannel = new FileOutputStream(dest).getChannel();
210 sourceChannel.transferTo(0, sourceChannel.size(), destinationChannel);
211 sourceChannel.close();
212 destinationChannel.close();
213 }
214
215 private void copyConfDirectory(File from, File dest) throws IOException {
216 if (from.isDirectory()) {
217 String files[] = from.list();
218
219 for (String file : files) {
220 File srcFile = new File(from, file);
221 if (srcFile.isFile() && !srcFile.getName().equals("activemq.xml")) {
222 File destFile = new File(dest, file);
223 context.print("Copying from: " + srcFile.getCanonicalPath() + "\n to: " + destFile.getCanonicalPath());
224 copyFile(srcFile, destFile);
225 }
226 }
227 } else {
228 throw new IOException(from + " is not a directory");
229 }
230 }
231
232 // replace a property place holder (paramName) with the paramValue
233 private String resolveParam(String paramName, String paramValue, String target) {
234 return target.replaceAll(paramName, paramValue);
235 }
236
237 // Embedded windows script data
238 private static final String winActivemqData =
239 "@echo off\n"
240 + "set ACTIVEMQ_HOME=\"${activemq.home}\"\n"
241 + "set ACTIVEMQ_BASE=\"${activemq.base}\"\n"
242 + "\n"
243 + "set PARAM=%1\n"
244 + ":getParam\n"
245 + "shift\n"
246 + "if \"%1\"==\"\" goto end\n"
247 + "set PARAM=%PARAM% %1\n"
248 + "goto getParam\n"
249 + ":end\n"
250 + "\n"
251 + "%ACTIVEMQ_HOME%/bin/activemq %PARAM%";
252
253
254 private String getUnixActivemqData() {
255 StringBuffer res = new StringBuffer();
256 res.append("## Figure out the ACTIVEMQ_BASE from the directory this script was run from\n");
257 res.append("PRG=\"$0\"\n");
258 res.append("progname=`basename \"$0\"`\n");
259 res.append("saveddir=`pwd`\n");
260 res.append("# need this for relative symlinks\n");
261 res.append("dirname_prg=`dirname \"$PRG\"`\n");
262 res.append("cd \"$dirname_prg\"\n");
263 res.append("while [ -h \"$PRG\" ] ; do\n");
264 res.append(" ls=`ls -ld \"$PRG\"`\n");
265 res.append(" link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n");
266 res.append(" if expr \"$link\" : '.*/.*' > /dev/null; then\n");
267 res.append(" PRG=\"$link\"\n");
268 res.append(" else\n");
269 res.append(" PRG=`dirname \"$PRG\"`\"/$link\"\n");
270 res.append(" fi\n");
271 res.append("done\n");
272 res.append("ACTIVEMQ_BASE=`dirname \"$PRG\"`/..\n");
273 res.append("cd \"$saveddir\"\n\n");
274 res.append("ACTIVEMQ_BASE=`cd \"$ACTIVEMQ_BASE\" && pwd`\n\n");
275 res.append("## Add system properties for this instance here (if needed), e.g\n");
276 res.append("#export ACTIVEMQ_OPTS_MEMORY=\"-Xms256M -Xmx1G\"\n");
277 res.append("#export ACTIVEMQ_OPTS=\"$ACTIVEMQ_OPTS_MEMORY -Dorg.apache.activemq.UseDedicatedTaskRunner=true -Djava.util.logging.config.file=logging.properties\"\n\n");
278 res.append("export ACTIVEMQ_HOME=${activemq.home}\n");
279 res.append("export ACTIVEMQ_BASE=$ACTIVEMQ_BASE\n\n");
280 res.append("${ACTIVEMQ_HOME}/bin/activemq \"$*\"");
281 return res.toString();
282 }
283
284 }