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}