| /* |
| * 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.karaf.admin.internal; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.PrintStream; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Scanner; |
| |
| import org.apache.karaf.admin.AdminService; |
| import org.apache.karaf.admin.Instance; |
| import org.apache.karaf.admin.InstanceSettings; |
| import org.fusesource.jansi.Ansi; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| public class AdminServiceImpl implements AdminService { |
| public static final String STORAGE_FILE = "instance.properties"; |
| public static final String BACKUP_EXTENSION = ".bak"; |
| private static final String FEATURES_CFG = "etc/org.apache.karaf.features.cfg"; |
| |
| private static final Logger LOGGER = LoggerFactory.getLogger(AdminServiceImpl.class); |
| |
| private Map<String, Instance> instances = new HashMap<String, Instance>(); |
| |
| private int defaultSshPortStart = 8101; |
| |
| private int defaultRmiRegistryPortStart = 1099; |
| |
| private int defaultRmiServerPortStart = 44444; |
| |
| private File storageLocation; |
| |
| private long stopTimeout = 30000; |
| |
| public File getStorageLocation() { |
| return storageLocation; |
| } |
| |
| public void setStorageLocation(File storage) { |
| this.storageLocation = storage; |
| } |
| |
| public long getStopTimeout() { |
| return stopTimeout; |
| } |
| |
| public void setStopTimeout(long stopTimeout) { |
| this.stopTimeout = stopTimeout; |
| } |
| |
| private Properties loadStorage(File location) throws IOException { |
| InputStream is = null; |
| try { |
| is = new FileInputStream(location); |
| Properties props = new Properties(); |
| props.load(is); |
| return props; |
| } finally { |
| if (is != null) { |
| is.close(); |
| } |
| } |
| } |
| |
| private void saveStorage(Properties props, File location, String comment) throws IOException { |
| OutputStream os = null; |
| try { |
| os = new FileOutputStream(location); |
| props.store(os, comment); |
| } finally { |
| if (os != null) { |
| os.close(); |
| } |
| } |
| } |
| |
| public synchronized void init() throws Exception { |
| try { |
| File storageFile = new File(storageLocation, STORAGE_FILE); |
| if (!storageFile.isFile()) { |
| if (storageFile.exists()) { |
| LOGGER.error("Instances storage location should be a file: " + storageFile); |
| } |
| return; |
| } |
| Properties storage = loadStorage(storageFile); |
| int count = Integer.parseInt(storage.getProperty("count", "0")); |
| defaultSshPortStart = Integer.parseInt(storage.getProperty("ssh.port", Integer.toString(defaultSshPortStart))); |
| defaultRmiRegistryPortStart = Integer.parseInt(storage.getProperty("rmi.registry.port", Integer.toString(defaultRmiRegistryPortStart))); |
| defaultRmiServerPortStart = Integer.parseInt(storage.getProperty("rmi.server.port", Integer.toString(defaultRmiServerPortStart))); |
| Map<String, Instance> newInstances = new HashMap<String, Instance>(); |
| for (int i = 0; i < count; i++) { |
| String name = storage.getProperty("item." + i + ".name", null); |
| String loc = storage.getProperty("item." + i + ".loc", null); |
| String opts = storage.getProperty("item." + i + ".opts", null); |
| int pid = Integer.parseInt(storage.getProperty("item." + i + ".pid", "0")); |
| boolean root = Boolean.parseBoolean(storage.getProperty("item." + i + ".root", "false")); |
| if (name != null) { |
| InstanceImpl instance = new InstanceImpl(this, name, loc, opts, root); |
| if (pid > 0) { |
| try { |
| instance.attach(pid); |
| } catch (IOException e) { |
| // Ignore |
| } |
| } |
| newInstances.put(name, instance); |
| } |
| } |
| instances = newInstances; |
| } catch (Exception e) { |
| LOGGER.warn("Unable to reload Karaf instance list", e); |
| } |
| } |
| |
| public synchronized Instance createInstance(String name, InstanceSettings settings) throws Exception { |
| if (instances.get(name) != null) { |
| throw new IllegalArgumentException("Instance '" + name + "' already exists"); |
| } |
| String loc = settings.getLocation() != null ? settings.getLocation() : name; |
| File karafBase = new File(loc); |
| if (!karafBase.isAbsolute()) { |
| karafBase = new File(storageLocation, loc); |
| } |
| int sshPort = settings.getSshPort(); |
| if (sshPort <= 0) { |
| sshPort = ++defaultSshPortStart; |
| } |
| int rmiRegistryPort = settings.getRmiRegistryPort(); |
| if (rmiRegistryPort <= 0) { |
| rmiRegistryPort = ++defaultRmiRegistryPortStart; |
| } |
| int rmiServerPort = settings.getRmiServerPort(); |
| if (rmiServerPort <= 0) { |
| rmiServerPort = ++defaultRmiServerPortStart; |
| } |
| println(Ansi.ansi().a("Creating new instance on SSH port ").a(sshPort).a(" and RMI ports ").a(rmiRegistryPort).a("/").a(rmiServerPort).a(" at: ").a(Ansi.Attribute.INTENSITY_BOLD).a(karafBase).a(Ansi.Attribute.RESET).toString()); |
| |
| mkdir(karafBase, "bin"); |
| mkdir(karafBase, "etc"); |
| mkdir(karafBase, "system"); |
| mkdir(karafBase, "deploy"); |
| mkdir(karafBase, "data"); |
| |
| copyResourceToDir(karafBase, "etc/config.properties", true); |
| copyResourceToDir(karafBase, "etc/jre.properties", true); |
| copyResourceToDir(karafBase, "etc/custom.properties", true); |
| copyResourceToDir(karafBase, "etc/java.util.logging.properties", true); |
| copyResourceToDir(karafBase, "etc/org.apache.felix.fileinstall-deploy.cfg", true); |
| copyResourceToDir(karafBase, "etc/org.apache.karaf.log.cfg", true); |
| copyResourceToDir(karafBase, FEATURES_CFG, true); |
| copyResourceToDir(karafBase, "etc/org.ops4j.pax.logging.cfg", true); |
| copyResourceToDir(karafBase, "etc/org.ops4j.pax.url.mvn.cfg", true); |
| copyResourceToDir(karafBase, "etc/startup.properties", true); |
| copyResourceToDir(karafBase, "etc/users.properties", true); |
| |
| HashMap<String, String> props = new HashMap<String, String>(); |
| props.put("${SUBST-KARAF-NAME}", name); |
| props.put("${SUBST-KARAF-HOME}", System.getProperty("karaf.home")); |
| props.put("${SUBST-KARAF-BASE}", karafBase.getPath()); |
| props.put("${SUBST-SSH-PORT}", Integer.toString(sshPort)); |
| props.put("${SUBST-RMI-REGISTRY-PORT}", Integer.toString(rmiRegistryPort)); |
| props.put("${SUBST-RMI-SERVER-PORT}", Integer.toString(rmiServerPort)); |
| copyFilteredResourceToDir(karafBase, "etc/system.properties", props); |
| copyFilteredResourceToDir(karafBase, "etc/org.apache.karaf.shell.cfg", props); |
| copyFilteredResourceToDir(karafBase, "etc/org.apache.karaf.management.cfg", props); |
| // If we use batch files, use batch files, else use bash scripts (even on cygwin) |
| boolean windows = System.getProperty("os.name").startsWith("Win"); |
| boolean cygwin = windows && new File( System.getProperty("karaf.home"), "bin/admin" ).exists(); |
| if( windows && !cygwin ) { |
| copyFilteredResourceToDir(karafBase, "bin/karaf.bat", props); |
| copyFilteredResourceToDir(karafBase, "bin/start.bat", props); |
| copyFilteredResourceToDir(karafBase, "bin/stop.bat", props); |
| } else { |
| copyFilteredResourceToDir(karafBase, "bin/karaf", props); |
| copyFilteredResourceToDir(karafBase, "bin/start", props); |
| copyFilteredResourceToDir(karafBase, "bin/stop", props); |
| if ( !cygwin ) { |
| chmod(new File(karafBase, "bin/karaf"), "a+x"); |
| chmod(new File(karafBase, "bin/start"), "a+x"); |
| chmod(new File(karafBase, "bin/stop"), "a+x"); |
| } |
| } |
| |
| handleFeatures(new File(karafBase, FEATURES_CFG), settings); |
| |
| String javaOpts = settings.getJavaOpts(); |
| if (javaOpts == null || javaOpts.length() == 0) { |
| javaOpts = "-server -Xmx512M -Dcom.sun.management.jmxremote"; |
| } |
| Instance instance = new InstanceImpl(this, name, karafBase.toString(), settings.getJavaOpts()); |
| instances.put(name, instance); |
| saveState(); |
| return instance; |
| } |
| |
| void handleFeatures(File featuresCfg, InstanceSettings settings) throws IOException { |
| Properties p = loadStorage(featuresCfg); |
| |
| appendToPropList(p, "featuresBoot", settings.getFeatures()); |
| appendToPropList(p, "featuresRepositories", settings.getFeatureURLs()); |
| saveStorage(p, featuresCfg, "Features Configuration"); |
| } |
| |
| private void appendToPropList(Properties p, String key, List<String> elements) { |
| if (elements == null) { |
| return; |
| } |
| StringBuilder sb = new StringBuilder(p.getProperty(key).trim()); |
| for (String f : elements) { |
| if (sb.length() > 0) { |
| sb.append(','); |
| } |
| sb.append(f); |
| } |
| p.setProperty(key, sb.toString()); |
| } |
| |
| public synchronized Instance[] getInstances() { |
| return instances.values().toArray(new Instance[0]); |
| } |
| |
| public synchronized Instance getInstance(String name) { |
| return instances.get(name); |
| } |
| |
| synchronized void forget(String name) { |
| instances.remove(name); |
| } |
| |
| public synchronized void renameInstance(String oldName, String newName) throws Exception { |
| if (instances.get(newName) != null) { |
| throw new IllegalArgumentException("Instance " + newName + " already exists"); |
| } |
| Instance instance = instances.get(oldName); |
| if (instance == null) { |
| throw new IllegalArgumentException("Instance " + oldName + " not found"); |
| } |
| if (instance.isRoot()) { |
| throw new IllegalArgumentException("Root instance cannot be renamed"); |
| } |
| if (instance.getPid() != 0) { |
| throw new IllegalStateException("Instance not stopped"); |
| } |
| |
| println(Ansi.ansi().a("Renaming instance ") |
| .a(Ansi.Attribute.INTENSITY_BOLD).a(oldName).a(Ansi.Attribute.RESET) |
| .a(" to ") |
| .a(Ansi.Attribute.INTENSITY_BOLD).a(newName).a(Ansi.Attribute.RESET).toString()); |
| // remove the old instance |
| instances.remove(oldName); |
| // update instance |
| instance.setName(newName); |
| // rename directory |
| String oldLocationPath = instance.getLocation(); |
| File oldLocation = new File(oldLocationPath); |
| String basedir = oldLocation.getParent(); |
| File newLocation = new File(basedir, newName); |
| oldLocation.renameTo(newLocation); |
| // update the instance location |
| instance.setLocation(newLocation.getPath()); |
| // create the properties map including the instance name and instance location |
| HashMap<String, String> props = new HashMap<String, String>(); |
| props.put(oldName, newName); |
| props.put(oldLocationPath, newLocation.getPath()); |
| // replace all references to the "old" name by the new one in etc/system.properties |
| // NB: it's replacement to avoid to override the user's changes |
| filterResource(newLocation, "etc/system.properties", props); |
| // replace all references to the "old" name by the new one in bin/karaf |
| filterResource(newLocation, "bin/karaf", props); |
| filterResource(newLocation, "bin/start", props); |
| filterResource(newLocation, "bin/stop", props); |
| filterResource(newLocation, "bin/karaf.bat", props); |
| filterResource(newLocation, "bin/start.bat", props); |
| filterResource(newLocation, "bin/stop.bat", props); |
| // add the renamed instances |
| instances.put(newName, instance); |
| // save instance definition in the instances.properties |
| saveState(); |
| } |
| |
| synchronized void saveState() throws IOException { |
| Properties storage = new Properties(); |
| Instance[] data = getInstances(); |
| storage.setProperty("ssh.port", Integer.toString(defaultSshPortStart)); |
| storage.setProperty("rmi.registry.port", Integer.toString(defaultRmiRegistryPortStart)); |
| storage.setProperty("rmi.server.port", Integer.toString(defaultRmiServerPortStart)); |
| storage.setProperty("count", Integer.toString(data.length)); |
| for (int i = 0; i < data.length; i++) { |
| storage.setProperty("item." + i + ".name", data[i].getName()); |
| storage.setProperty("item." + i + ".root", data[i].isRoot() + ""); |
| storage.setProperty("item." + i + ".loc", data[i].getLocation()); |
| storage.setProperty("item." + i + ".pid", Integer.toString(data[i].getPid())); |
| storage.setProperty("item." + i + ".opts", data[i].getJavaOpts() != null ? data[i].getJavaOpts() : ""); |
| } |
| saveStorage(storage, new File(storageLocation, STORAGE_FILE), "Admin Service storage"); |
| } |
| |
| private void copyResourceToDir(File target, String resource, boolean text) throws Exception { |
| File outFile = new File(target, resource); |
| if( !outFile.exists() ) { |
| println(Ansi.ansi().a("Creating file: ").a(Ansi.Attribute.INTENSITY_BOLD).a(outFile.getPath()).a(Ansi.Attribute.RESET).toString()); |
| InputStream is = getClass().getClassLoader().getResourceAsStream("org/apache/karaf/admin/" + resource); |
| try { |
| if( text ) { |
| // Read it line at a time so that we can use the platform line ending when we write it out. |
| PrintStream out = new PrintStream(new FileOutputStream(outFile)); |
| try { |
| Scanner scanner = new Scanner(is); |
| while (scanner.hasNextLine() ) { |
| String line = scanner.nextLine(); |
| out.println(line); |
| } |
| } finally { |
| safeClose(out); |
| } |
| } else { |
| // Binary so just write it out the way it came in. |
| FileOutputStream out = new FileOutputStream(new File(target, resource)); |
| try { |
| int c=0; |
| while((c=is.read())>=0) { |
| out.write(c); |
| } |
| } finally { |
| safeClose(out); |
| } |
| } |
| } finally { |
| safeClose(is); |
| } |
| } |
| } |
| |
| private void println(String st) { |
| System.out.println(st); |
| } |
| |
| private void filterResource(File basedir, String path, HashMap<String, String> props) throws Exception { |
| File file = new File(basedir, path); |
| File bak = new File(basedir, path + BACKUP_EXTENSION); |
| if (!file.exists()) { |
| return; |
| } |
| // rename the file to the backup one |
| file.renameTo(bak); |
| // copy and filter the bak file back to the original name |
| copyAndFilterResource(new FileInputStream(bak), new FileOutputStream(file), props); |
| // remove the bak file |
| bak.delete(); |
| } |
| |
| private void copyFilteredResourceToDir(File target, String resource, HashMap<String, String> props) throws Exception { |
| File outFile = new File(target, resource); |
| if( !outFile.exists() ) { |
| println(Ansi.ansi().a("Creating file: ").a(Ansi.Attribute.INTENSITY_BOLD).a(outFile.getPath()).a(Ansi.Attribute.RESET).toString()); |
| InputStream is = getClass().getClassLoader().getResourceAsStream("org/apache/karaf/admin/" + resource); |
| copyAndFilterResource(is, new FileOutputStream(outFile), props); |
| } |
| } |
| |
| private void copyAndFilterResource(InputStream source, OutputStream target, HashMap<String, String> props) throws Exception { |
| try { |
| // read it line at a time so that we can use the platform line ending when we write it out. |
| PrintStream out = new PrintStream(target); |
| try { |
| Scanner scanner = new Scanner(source); |
| while (scanner.hasNextLine()) { |
| String line = scanner.nextLine(); |
| line = filter(line, props); |
| out.println(line); |
| } |
| } finally { |
| safeClose(out); |
| } |
| } finally { |
| safeClose(source); |
| } |
| } |
| |
| private void safeClose(InputStream is) throws IOException { |
| if (is == null) { |
| return; |
| } |
| try { |
| is.close(); |
| } catch (Throwable ignore) { |
| } |
| } |
| |
| private void safeClose(OutputStream is) throws IOException { |
| if (is == null) { |
| return; |
| } |
| try { |
| is.close(); |
| } catch (Throwable ignore) { |
| } |
| } |
| |
| private String filter(String line, HashMap<String, String> props) { |
| for (Map.Entry<String, String> i : props.entrySet()) { |
| int p1 = line.indexOf(i.getKey()); |
| if( p1 >= 0 ) { |
| String l1 = line.substring(0, p1); |
| String l2 = line.substring(p1+i.getKey().length()); |
| line = l1+i.getValue()+l2; |
| } |
| } |
| return line; |
| } |
| |
| private void mkdir(File karafBase, String path) { |
| File file = new File(karafBase, path); |
| if( !file.exists() ) { |
| println(Ansi.ansi().a("Creating dir: ").a(Ansi.Attribute.INTENSITY_BOLD).a(file.getPath()).a(Ansi.Attribute.RESET).toString()); |
| file.mkdirs(); |
| } |
| } |
| |
| private int chmod(File serviceFile, String mode) throws Exception { |
| ProcessBuilder builder = new ProcessBuilder(); |
| builder.command("chmod", mode, serviceFile.getCanonicalPath()); |
| Process p = builder.start(); |
| |
| // gnodet: Fix SMX4KNL-46: cpu goes to 100% after running the 'admin create' command |
| // Not sure exactly what happens, but commenting the process io redirection seems |
| // to work around the problem. |
| // |
| //PumpStreamHandler handler = new PumpStreamHandler(io.inputStream, io.outputStream, io.errorStream); |
| //handler.attach(p); |
| //handler.start(); |
| int status = p.waitFor(); |
| //handler.stop(); |
| return status; |
| } |
| |
| } |