/* * 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.openejb.server.cli; import jline.ConsoleReader; import jline.FileNameCompletor; import jline.SimpleCompletor; import org.apache.openejb.config.ConfigurableClasspathArchive; import org.apache.openejb.server.cli.command.AbstractCommand; import org.apache.openejb.server.cli.command.Command; import org.apache.openejb.util.LogCategory; import org.apache.openejb.util.Logger; import org.apache.openejb.util.OpenEJBScripter; import org.apache.openejb.util.OpenEjbVersion; import org.apache.xbean.finder.Annotated; import org.apache.xbean.finder.AnnotationFinder; import org.apache.xbean.finder.IAnnotationFinder; import org.apache.xbean.finder.UrlSet; import org.apache.xbean.recipe.ObjectRecipe; import org.apache.xbean.recipe.Option; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Collections; import java.util.Map; import java.util.Properties; import java.util.TreeMap; public class CliRunnable implements Runnable { private static final Logger LOGGER = Logger.getInstance(LogCategory.OPENEJB_SERVER, CliRunnable.class); private static final String BRANDING_FILE = "branding.properties"; private static final String WELCOME_KEY_PREFIX = "welcome_"; private static final String WELCOME_COMMON_KEY = WELCOME_KEY_PREFIX + "common"; private static final String WELCOME_OPENEJB_KEY = WELCOME_KEY_PREFIX + "openejb"; private static final String WELCOME_TOMEE_KEY = WELCOME_KEY_PREFIX + "tomee"; public static final String TOMEE_NAME = "TomEE"; public static final String OPENEJB_NAME = "OpenEJB"; public static final String EXIT_COMMAND = "exit"; private static final String OS_LINE_SEP = System.getProperty("line.separator"); private static final String NAME; private static final String PROMPT; private static final String PROMPT_SUFFIX = "> "; private static final Properties PROPERTIES = new Properties(); private static final boolean tomee; private static final Map<String, Class<?>> COMMANDS = new TreeMap<String, Class<?>>(); private static final OpenEJBScripter scripter = new OpenEJBScripter(); static { String name = OPENEJB_NAME; try { CliRunnable.class.getClassLoader().loadClass("org.apache.tomee.loader.TomcatHook"); name = TOMEE_NAME; } catch (ClassNotFoundException cnfe) { // ignored, we are using a simple OpenEJB server } tomee = TOMEE_NAME.equals(name); NAME = name; PROMPT = NAME.toLowerCase() + PROMPT_SUFFIX; try { PROPERTIES.load(CliRunnable.class.getClassLoader().getResourceAsStream(BRANDING_FILE)); } catch (IOException e) { // no-op } final ClassLoader loader = CliRunnable.class.getClassLoader(); try { UrlSet urlSet = new UrlSet(loader).excludeJvm(); urlSet = urlSet.exclude(loader.getParent()); //noinspection unchecked final IAnnotationFinder finder = new AnnotationFinder(new ConfigurableClasspathArchive(new ConfigurableClasspathArchive.FakeModule(loader, Collections.EMPTY_MAP), true, urlSet.getUrls())); for (final Annotated<Class<?>> cmd : finder.findMetaAnnotatedClasses(Command.class)) { try { final Command annotation = cmd.getAnnotation(Command.class); final String key = annotation.name(); if (!COMMANDS.containsKey(key)) { COMMANDS.put(key, cmd.get()); } else { LOGGER.warning("command " + key + " already exists, this one will be ignored ( " + annotation.description() + ")"); } } catch (Exception e) { // command ignored } } } catch (RuntimeException e) { LOGGER.error("an error occured while getting commands", e); } catch (IOException e) { LOGGER.error("can't get commands"); } } public String lineSep; private OutputStream err; private OutputStream out; private InputStream sin; private String username; private final String bind; private final int port; public CliRunnable(final String bind, final int port) { this(bind, port, PROMPT, null); } public CliRunnable(final String bind, final int port, final String username, final String sep) { this.bind = bind; this.port = port; this.username = username; if (sep != null) { // workaround to force ConsoleReader to use another line.separator lineSep = sep; System.setProperty("line.separator", sep); try { // just to force the loading of this class with the set line.separator // because ConsoleReader.CR is a constant and we need sometimes another value // not a big issue but keeping this as a workaround new ConsoleReader(); } catch (IOException ignored) { // no-op } finally { System.setProperty("line.separator", OS_LINE_SEP); } } else { lineSep = OS_LINE_SEP; } } public void setUsername(final String username) { this.username = username; } public void setInputStream(final InputStream in) { sin = in; } public void setOutputStream(final OutputStream out) { this.out = out; } public void setErrorStream(final OutputStream err) { this.err = err; } public void start() throws IOException { new Thread(this, "OpenEJB Cli").start(); } public void destroy() { // no-op } public void clean() { OpenEJBScripter.clearEngines(); } @Override public void run() { clean(); try { final StreamManager streamManager = new StreamManager(out, err, lineSep); final ConsoleReader reader = new ConsoleReader(sin, streamManager.getSout()); reader.addCompletor(new FileNameCompletor()); reader.addCompletor(new SimpleCompletor(COMMANDS.keySet().toArray(new String[COMMANDS.size()]))); // TODO : add completers String line; final StringBuilder builtWelcome = new StringBuilder("Apache OpenEJB ") .append(OpenEjbVersion.get().getVersion()) .append(" build: ") .append(OpenEjbVersion.get().getDate()) .append("-") .append(OpenEjbVersion.get().getTime()) .append(lineSep); if (tomee) { builtWelcome.append(OS_LINE_SEP).append(PROPERTIES.getProperty(WELCOME_TOMEE_KEY)); } else { builtWelcome.append(OS_LINE_SEP).append(PROPERTIES.getProperty(WELCOME_OPENEJB_KEY)); } builtWelcome.append(lineSep).append(PROPERTIES.getProperty(WELCOME_COMMON_KEY)); streamManager.writeOut(OpenEjbVersion.get().getUrl()); streamManager.writeOut(builtWelcome.toString() .replace("$bind", bind) .replace("$port", Integer.toString(port)) .replace("$name", NAME) .replace(OS_LINE_SEP, lineSep)); while ((line = reader.readLine(prompt())) != null) { // exit simply let us go out of the loop // do we need a command for it? if (EXIT_COMMAND.equals(line)) { break; } Class<?> cmdClass = null; String key = null; for (final Map.Entry<String, Class<?>> cmd : COMMANDS.entrySet()) { if (line.startsWith(cmd.getKey())) { cmdClass = cmd.getValue(); key = cmd.getKey(); break; } } if (cmdClass != null) { final ObjectRecipe recipe = new ObjectRecipe(cmdClass); recipe.setProperty("streamManager", streamManager); recipe.setProperty("command", line); recipe.setProperty("scripter", scripter); recipe.setProperty("commands", COMMANDS); recipe.allow(Option.CASE_INSENSITIVE_PROPERTIES); recipe.allow(Option.IGNORE_MISSING_PROPERTIES); recipe.allow(Option.NAMED_PARAMETERS); try { final AbstractCommand cmdInstance = (AbstractCommand) recipe.create(); cmdInstance.execute(trunc(line, key)); } catch (Exception e) { streamManager.writeErr(e); } } else { streamManager.writeErr("sorry i don't understand '" + line + "'"); } } clean(); } catch (IOException e) { clean(); throw new CliRuntimeException(e); } } private String trunc(final String line, final String cmd) { if (line.length() == cmd.length()) { return ""; } final String trunc = line.substring(cmd.length()); int idx = 0; while (trunc.charAt(idx) == ' ') { idx++; } if (idx < trunc.length()) { return trunc.substring(idx); } return ""; } private String prompt() { final StringBuilder prompt = new StringBuilder(""); if (username != null) { prompt.append(username); } else { prompt.append(PROMPT); } prompt.append(" @ ") .append(bind).append(":").append(port) .append(PROMPT_SUFFIX); return prompt.toString(); } }