| /** |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.activemq.artemis.cli.commands; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.nio.file.Files; |
| import java.nio.file.attribute.PosixFilePermission; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import io.airlift.airline.Arguments; |
| import io.airlift.airline.Command; |
| import io.airlift.airline.Option; |
| import org.apache.activemq.artemis.core.asyncio.impl.AsynchronousFileImpl; |
| |
| import static java.nio.file.attribute.PosixFilePermission.GROUP_EXECUTE; |
| import static java.nio.file.attribute.PosixFilePermission.GROUP_READ; |
| import static java.nio.file.attribute.PosixFilePermission.GROUP_WRITE; |
| import static java.nio.file.attribute.PosixFilePermission.OTHERS_EXECUTE; |
| import static java.nio.file.attribute.PosixFilePermission.OTHERS_READ; |
| import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE; |
| import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; |
| import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; |
| |
| /** |
| * CLI action that creates a broker instance directory. |
| */ |
| @Command(name = "create", description = "creates a new broker instance") |
| public class Create extends InputAbstract |
| { |
| private static final Integer DEFAULT_PORT = 61616; |
| |
| private static final Integer AMQP_PORT = 5672; |
| |
| private static final Integer STOMP_PORT = 61613; |
| |
| private static final Integer HQ_PORT = 5445; |
| |
| private static final Integer HTTP_PORT = 8161; |
| |
| public static final String BIN_ARTEMIS_CMD = "bin/artemis.cmd"; |
| public static final String BIN_ARTEMIS_SERVICE_EXE = "bin/artemis-service.exe"; |
| public static final String BIN_ARTEMIS_SERVICE_XML = "bin/artemis-service.xml"; |
| public static final String ETC_ARTEMIS_PROFILE_CMD = "etc/artemis.profile.cmd"; |
| public static final String BIN_ARTEMIS = "bin/artemis"; |
| public static final String BIN_ARTEMIS_SERVICE = "bin/artemis-service"; |
| public static final String ETC_ARTEMIS_PROFILE = "etc/artemis.profile"; |
| public static final String ETC_LOGGING_PROPERTIES = "etc/logging.properties"; |
| public static final String ETC_BOOTSTRAP_XML = "etc/bootstrap.xml"; |
| public static final String ETC_BROKER_XML = "etc/broker.xml"; |
| public static final String ETC_ARTEMIS_ROLES_PROPERTIES = "etc/artemis-roles.properties"; |
| public static final String ETC_ARTEMIS_USERS_PROPERTIES = "etc/artemis-users.properties"; |
| public static final String ETC_REPLICATED_SETTINGS_TXT = "etc/replicated-settings.txt"; |
| public static final String ETC_SHARED_STORE_SETTINGS_TXT = "etc/shared-store-settings.txt"; |
| public static final String ETC_CLUSTER_SECURITY_SETTINGS_TXT = "etc/cluster-security-settings.txt"; |
| public static final String ETC_CLUSTER_SETTINGS_TXT = "etc/cluster-settings.txt"; |
| public static final String ETC_CONNECTOR_SETTINGS_TXT = "etc/connector-settings.txt"; |
| public static final String ETC_BOOTSTRAP_WEB_SETTINGS_TXT = "etc/bootstrap-web-settings.txt"; |
| |
| @Arguments(description = "The instance directory to hold the broker's configuration and data", required = true) |
| File directory; |
| |
| @Option(name = "--host", description = "The host name of the broker (Default: 0.0.0.0 or input if clustered)") |
| String host; |
| |
| @Option(name = "--port-offset", description = "Off sets the default ports") |
| int portOffset; |
| |
| @Option(name = "--force", description = "Overwrite configuration at destination directory") |
| boolean force; |
| |
| @Option(name = "--home", description = "Directory where ActiveMQ Artemis is installed") |
| File home; |
| |
| @Option(name = "--data", description = "Directory where ActiveMQ Data is used. Path are relative to artemis.instance/bin") |
| String data = "../data"; |
| |
| @Option(name = "--clustered", description = "Enable clustering") |
| boolean clustered = false; |
| |
| @Option(name = "--max-hops", description = "Number of hops on the cluster configuration") |
| int maxHops = 0; |
| |
| @Option(name = "--replicated", description = "Enable broker replication") |
| boolean replicated = false; |
| |
| @Option(name = "--shared-store", description = "Enable broker shared store") |
| boolean sharedStore = false; |
| |
| @Option(name = "--cluster-user", description = "The cluster user to use for clustering. (Default: input)") |
| String clusterUser = null; |
| |
| @Option(name = "--cluster-password", description = "The cluster password to use for clustering. (Default: input)") |
| String clusterPassword = null; |
| |
| @Option(name = "--encoding", description = "The encoding that text files should use") |
| String encoding = "UTF-8"; |
| |
| @Option(name = "--java-options", description = "Extra java options to be passed to the profile") |
| String javaOptions = ""; |
| |
| @Option(name = "--allow-anonymous", description = "Enables anonymous configuration on security (Default: input)") |
| Boolean allowAnonymous = null; |
| |
| @Option(name = "--user", description = "The username (Default: input)") |
| String user; |
| |
| @Option(name = "--password", description = "The user's password (Default: input)") |
| String password; |
| |
| @Option(name = "--role", description = "The name for the role created (Default: amq)") |
| String role; |
| |
| @Option(name = "--no-web", description = "This will remove the web server definition from bootstrap.xml") |
| boolean noWeb; |
| |
| boolean IS_WINDOWS; |
| |
| boolean IS_CYGWIN; |
| |
| public int getMaxHops() |
| { |
| return maxHops; |
| } |
| |
| public void setMaxHops(int maxHops) |
| { |
| this.maxHops = maxHops; |
| } |
| |
| public boolean isNoWeb() |
| { |
| return noWeb; |
| } |
| |
| public void setNoWeb(boolean noWeb) |
| { |
| this.noWeb = noWeb; |
| } |
| |
| public int getPortOffset() |
| { |
| return portOffset; |
| } |
| |
| public void setPortOffset(int portOffset) |
| { |
| this.portOffset = portOffset; |
| } |
| |
| public String getJavaOptions() |
| { |
| return javaOptions; |
| } |
| |
| public void setJavaOptions(String javaOptions) |
| { |
| this.javaOptions = javaOptions; |
| } |
| |
| public File getInstance() |
| { |
| return directory; |
| } |
| |
| public void setInstance(File directory) |
| { |
| this.directory = directory; |
| } |
| |
| public String getHost() |
| { |
| if (host == null) |
| { |
| host = "0.0.0.0"; |
| } |
| return host; |
| } |
| |
| public String getHostForClustered() |
| { |
| if (getHost().equals("0.0.0.0")) |
| { |
| host = input("--host", "Host " + host + " is not valid for clustering, please provide a valid IP or hostname", "localhost"); |
| } |
| |
| return host; |
| } |
| |
| public void setHost(String host) |
| { |
| this.host = host; |
| } |
| |
| public boolean isForce() |
| { |
| return force; |
| } |
| |
| public void setForce(boolean force) |
| { |
| this.force = force; |
| } |
| |
| public File getHome() |
| { |
| if (home == null) |
| { |
| home = new File(System.getProperty("artemis.home")); |
| } |
| return home; |
| } |
| |
| public void setHome(File home) |
| { |
| this.home = home; |
| } |
| |
| public boolean isClustered() |
| { |
| return clustered; |
| } |
| |
| public void setClustered(boolean clustered) |
| { |
| this.clustered = clustered; |
| } |
| |
| public boolean isReplicated() |
| { |
| return replicated; |
| } |
| |
| public void setReplicated(boolean replicated) |
| { |
| this.replicated = replicated; |
| } |
| |
| public boolean isSharedStore() |
| { |
| return sharedStore; |
| } |
| |
| public void setSharedStore(boolean sharedStore) |
| { |
| this.sharedStore = sharedStore; |
| } |
| |
| public String getEncoding() |
| { |
| return encoding; |
| } |
| |
| public void setEncoding(String encoding) |
| { |
| this.encoding = encoding; |
| } |
| |
| public String getData() |
| { |
| return data; |
| } |
| |
| public void setData(String data) |
| { |
| this.data = data; |
| } |
| |
| public String getClusterUser() |
| { |
| if (clusterUser == null) |
| { |
| clusterUser = input("--cluster-user", "Please provide the username:", "cluster-admin"); |
| } |
| return clusterUser; |
| } |
| |
| public void setClusterUser(String clusterUser) |
| { |
| this.clusterUser = clusterUser; |
| } |
| |
| public String getClusterPassword() |
| { |
| if (clusterPassword == null) |
| { |
| clusterPassword = inputPassword("--cluster-password", "Please enter the password:", "password-admin"); |
| } |
| return clusterPassword; |
| } |
| |
| public void setClusterPassword(String clusterPassword) |
| { |
| this.clusterPassword = clusterPassword; |
| } |
| |
| public boolean isAllowAnonymous() |
| { |
| if (allowAnonymous == null) |
| { |
| String value = input("--allow-anonymous", "Allow anonymous access? (Y/N):", "Y"); |
| allowAnonymous = Boolean.valueOf(value.toLowerCase().equals("y")); |
| } |
| return allowAnonymous.booleanValue(); |
| } |
| |
| public void setAllowAnonymous(boolean allowGuest) |
| { |
| this.allowAnonymous = Boolean.valueOf(allowGuest); |
| } |
| |
| public String getPassword() |
| { |
| |
| if (password == null) |
| { |
| this.password = inputPassword("--password", "Please provide the default password:", "admin"); |
| } |
| |
| return password; |
| } |
| |
| public void setPassword(String password) |
| { |
| this.password = password; |
| } |
| |
| public String getUser() |
| { |
| if (user == null) |
| { |
| user = input("--user", "Please provide the default username:", "admin"); |
| } |
| return user; |
| } |
| |
| public void setUser(String user) |
| { |
| this.user = user; |
| } |
| |
| public String getRole() |
| { |
| if (role == null) |
| { |
| role = "amq"; |
| } |
| return role; |
| } |
| |
| public void setRole(String role) |
| { |
| this.role = role; |
| } |
| |
| @Override |
| public Object execute(ActionContext context) throws Exception |
| { |
| super.execute(context); |
| |
| try |
| { |
| return run(context); |
| } |
| catch (Throwable e) |
| { |
| e.printStackTrace(context.err); |
| throw e; |
| } |
| } |
| |
| |
| /** This method is made public for the testsuite */ |
| public InputStream openStream(String source) |
| { |
| return this.getClass().getResourceAsStream(source); |
| } |
| |
| |
| public Object run(ActionContext context) throws Exception |
| { |
| IS_WINDOWS = System.getProperty("os.name").toLowerCase().trim().startsWith("win"); |
| IS_CYGWIN = IS_WINDOWS && "cygwin".equals(System.getenv("OSTYPE")); |
| |
| context.out.println(String.format("Creating ActiveMQ Artemis instance at: %s", directory.getCanonicalPath())); |
| |
| HashMap<String, String> filters = new HashMap<String, String>(); |
| |
| if (replicated) |
| { |
| clustered = true; |
| filters.put("${replicated.settings}", readTextFile(ETC_REPLICATED_SETTINGS_TXT)); |
| } |
| else |
| { |
| filters.put("${replicated.settings}", ""); |
| } |
| |
| if (sharedStore) |
| { |
| clustered = true; |
| filters.put("${shared-store.settings}", readTextFile(ETC_SHARED_STORE_SETTINGS_TXT)); |
| } |
| else |
| { |
| filters.put("${shared-store.settings}", ""); |
| } |
| |
| if (IS_WINDOWS || !AsynchronousFileImpl.isLoaded()) |
| { |
| filters.put("${journal.settings}", "NIO"); |
| } |
| else |
| { |
| filters.put("${journal.settings}", "ASYNCIO"); |
| } |
| |
| filters.put("${user}", System.getProperty("user.name", "")); |
| filters.put("${default.port}", String.valueOf(DEFAULT_PORT + portOffset)); |
| filters.put("${amqp.port}", String.valueOf(AMQP_PORT + portOffset)); |
| filters.put("${stomp.port}", String.valueOf(STOMP_PORT + portOffset)); |
| filters.put("${hq.port}", String.valueOf(HQ_PORT + portOffset)); |
| filters.put("${http.port}", String.valueOf(HTTP_PORT + portOffset)); |
| filters.put("${data.dir}", data); |
| filters.put("${max-hops}", String.valueOf(maxHops)); |
| filters.put("${user}", getUser()); |
| filters.put("${password}", getPassword()); |
| filters.put("${role}", getRole()); |
| |
| |
| if (clustered) |
| { |
| filters.put("${host}", getHostForClustered()); |
| String connectorSettings = readTextFile(ETC_CONNECTOR_SETTINGS_TXT); |
| connectorSettings = applyFilters(connectorSettings, filters); |
| |
| filters.put("${connector-config.settings}", connectorSettings); |
| filters.put("${cluster-security.settings}", readTextFile(ETC_CLUSTER_SECURITY_SETTINGS_TXT)); |
| filters.put("${cluster.settings}", applyFilters(readTextFile(ETC_CLUSTER_SETTINGS_TXT), filters)); |
| filters.put("${cluster-user}", getClusterUser()); |
| filters.put("${cluster-password}", getClusterPassword()); |
| } |
| else |
| { |
| filters.put("${host}", getHost()); |
| filters.put("${connector-config.settings}", ""); |
| filters.put("${cluster-security.settings}", ""); |
| filters.put("${cluster.settings}", ""); |
| filters.put("${cluster-user}", ""); |
| filters.put("${cluster-password}", ""); |
| } |
| |
| |
| if (home != null) |
| { |
| filters.put("${home}", path(home, false)); |
| } |
| filters.put("${artemis.home}", path(getHome().toString(), false)); |
| filters.put("${artemis.instance}", path(directory, false)); |
| filters.put("${artemis.instance.name}", directory.getName()); |
| filters.put("${java.home}", path(System.getProperty("java.home"), false)); |
| |
| new File(directory, "bin").mkdirs(); |
| new File(directory, "etc").mkdirs(); |
| new File(directory, "log").mkdirs(); |
| new File(directory, "tmp").mkdirs(); |
| new File(directory, "data").mkdirs(); |
| |
| if (javaOptions == null || javaOptions.length() == 0) |
| { |
| javaOptions = ""; |
| } |
| |
| filters.put("${java-opts}", javaOptions); |
| |
| if (IS_WINDOWS) |
| { |
| write(BIN_ARTEMIS_CMD, null, false); |
| write(BIN_ARTEMIS_SERVICE_EXE); |
| write(BIN_ARTEMIS_SERVICE_XML, filters, false); |
| write(ETC_ARTEMIS_PROFILE_CMD, filters, false); |
| } |
| |
| if (!IS_WINDOWS || IS_CYGWIN) |
| { |
| write(BIN_ARTEMIS, null, true); |
| makeExec(BIN_ARTEMIS); |
| write(BIN_ARTEMIS_SERVICE, null, true); |
| makeExec(BIN_ARTEMIS_SERVICE); |
| write(ETC_ARTEMIS_PROFILE, filters, true); |
| makeExec(ETC_ARTEMIS_PROFILE); |
| } |
| |
| write(ETC_LOGGING_PROPERTIES, null, false); |
| |
| if (isAllowAnonymous()) |
| { |
| filters.put("${bootstrap.guest}", "default-user=\"" + getUser() + "\""); |
| } |
| else |
| { |
| filters.put("${bootstrap.guest}", ""); |
| } |
| |
| |
| if (noWeb) |
| { |
| filters.put("${bootstrap-web-settings}", ""); |
| } |
| else |
| { |
| filters.put("${bootstrap-web-settings}", applyFilters(readTextFile(ETC_BOOTSTRAP_WEB_SETTINGS_TXT), filters)); |
| } |
| |
| |
| |
| write(ETC_BOOTSTRAP_XML, filters, false); |
| write(ETC_BROKER_XML, filters, false); |
| write(ETC_ARTEMIS_ROLES_PROPERTIES, filters, false); |
| write(ETC_ARTEMIS_USERS_PROPERTIES, filters, false); |
| |
| context.out.println(""); |
| context.out.println("You can now start the broker by executing: "); |
| context.out.println(""); |
| context.out.println(String.format(" \"%s\" run", path(new File(directory, "bin/artemis"), true))); |
| |
| File service = new File(directory, BIN_ARTEMIS_SERVICE); |
| context.out.println(""); |
| |
| if (!IS_WINDOWS || IS_CYGWIN) |
| { |
| |
| // Does it look like we are on a System V init system? |
| if (new File("/etc/init.d/").isDirectory()) |
| { |
| context.out.println("Or you can setup the broker as system service and run it in the background:"); |
| context.out.println(""); |
| context.out.println(" sudo ln -s \"%s\" /etc/init.d/".format(service.getCanonicalPath())); |
| context.out.println(" /etc/init.d/artemis-service start"); |
| context.out.println(""); |
| |
| } |
| else |
| { |
| |
| context.out.println("Or you can run the broker in the background using:"); |
| context.out.println(""); |
| context.out.println(String.format(" \"%s\" start", path(service, true))); |
| context.out.println(""); |
| } |
| } |
| |
| if (IS_WINDOWS) |
| { |
| service = new File(directory, BIN_ARTEMIS_SERVICE_EXE); |
| context.out.println("Or you can setup the broker as Windows service and run it in the background:"); |
| context.out.println(""); |
| context.out.println(String.format(" \"%s\" install", path(service, true))); |
| context.out.println(String.format(" \"%s\" start", path(service, true))); |
| context.out.println(""); |
| context.out.println(" To stop the windows service:"); |
| context.out.println(String.format(" \"%s\" stop", path(service, true))); |
| context.out.println(""); |
| context.out.println(" To uninstall the windows service"); |
| context.out.println(String.format(" \"%s\" uninstall", path(service, true))); |
| } |
| |
| return null; |
| } |
| |
| private void makeExec(String path) throws IOException |
| { |
| try |
| { |
| File file = new File(directory, path); |
| Files.setPosixFilePermissions(file.toPath(), new HashSet<PosixFilePermission>(Arrays.asList( |
| OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, |
| GROUP_READ, GROUP_WRITE, GROUP_EXECUTE, |
| OTHERS_READ, OTHERS_EXECUTE |
| ))); |
| } |
| catch (Throwable ignore) |
| { |
| // Our best effort was not good enough :) |
| } |
| } |
| |
| String path(String value, boolean unixPaths) throws IOException |
| { |
| return path(new File(value), unixPaths); |
| } |
| |
| private String path(File value, boolean unixPaths) throws IOException |
| { |
| if (unixPaths && IS_CYGWIN) |
| { |
| return value.getCanonicalPath(); |
| } |
| else |
| { |
| return value.getCanonicalPath(); |
| } |
| } |
| |
| private void write(String source, HashMap<String, String> filters, boolean unixTarget) throws IOException |
| { |
| write(source, new File(directory, source), filters, unixTarget); |
| } |
| |
| private void write(String source, File target, HashMap<String, String> filters, boolean unixTarget) throws IOException |
| { |
| if (target.exists() && !force) |
| { |
| throw new RuntimeException(String.format("The file '%s' already exists. Use --force to overwrite.", target)); |
| } |
| |
| String content = applyFilters(readTextFile(source), filters); |
| |
| // and then writing out in the new target encoding.. Let's also replace \n with the values |
| // that is correct for the current platform. |
| String separator = unixTarget && IS_CYGWIN ? "\n" : System.getProperty("line.separator"); |
| content = content.replaceAll("\\r?\\n", Matcher.quoteReplacement(separator)); |
| ByteArrayInputStream in = new ByteArrayInputStream(content.getBytes(encoding)); |
| |
| try (FileOutputStream fout = new FileOutputStream(target)) |
| { |
| copy(in, fout); |
| } |
| } |
| |
| private String applyFilters(String content, HashMap<String, String> filters) throws IOException |
| { |
| |
| if (filters != null) |
| { |
| for (Map.Entry<String, String> entry : filters.entrySet()) |
| { |
| content = replace(content, entry.getKey(), entry.getValue()); |
| } |
| } |
| return content; |
| } |
| |
| private String readTextFile(String source) throws IOException |
| { |
| ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| try (InputStream in = openStream(source)) |
| { |
| copy(in, out); |
| } |
| return new String(out.toByteArray(), "UTF-8"); |
| } |
| |
| private void write(String source) throws IOException |
| { |
| File target = new File(directory, source); |
| if (target.exists() && !force) |
| { |
| throw new RuntimeException(String.format("The file '%s' already exists. Use --force to overwrite.", target)); |
| } |
| try (FileOutputStream fout = new FileOutputStream(target)) |
| { |
| try (InputStream in = openStream(source)) |
| { |
| copy(in, fout); |
| } |
| } |
| } |
| |
| private String replace(String content, String key, String value) |
| { |
| return content.replaceAll(Pattern.quote(key), Matcher.quoteReplacement(value)); |
| } |
| |
| private void copy(InputStream is, OutputStream os) throws IOException |
| { |
| byte[] buffer = new byte[1024 * 4]; |
| int c = is.read(buffer); |
| while (c >= 0) |
| { |
| os.write(buffer, 0, c); |
| c = is.read(buffer); |
| } |
| } |
| |
| } |