http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
new file mode 100644
index 0000000..f162ca4
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
@@ -0,0 +1,443 @@
+/*
+ * 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.brooklyn.entity.java;
+
+import static org.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.internal.EntityLocal;
+import org.apache.brooklyn.effector.core.EffectorTasks;
+import org.apache.brooklyn.entity.core.Attributes;
+import org.apache.brooklyn.entity.core.Entities;
+import org.apache.brooklyn.entity.core.EntityInternal;
+import 
org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.base.Splitter;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.gson.internal.Primitives;
+
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.sensor.ssh.SshEffectorTasks;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.core.internal.ssh.ShellTool;
+import org.apache.brooklyn.util.core.task.DynamicTasks;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.core.task.ssh.SshTasks;
+import org.apache.brooklyn.util.core.task.system.ProcessTaskFactory;
+import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes;
+
+/**
+ * The SSH implementation of the {@link 
org.apache.brooklyn.entity.java.JavaSoftwareProcessDriver}.
+ */
+public abstract class JavaSoftwareProcessSshDriver extends 
AbstractSoftwareProcessSshDriver implements JavaSoftwareProcessDriver {
+
+    public static final Logger log = 
LoggerFactory.getLogger(JavaSoftwareProcessSshDriver.class);
+
+    public static final List<List<String>> MUTUALLY_EXCLUSIVE_OPTS = 
ImmutableList.<List<String>> of(ImmutableList.of("-client",
+            "-server"));
+
+    public static final List<String> KEY_VAL_OPT_PREFIXES = 
ImmutableList.of("-Xmx", "-Xms", "-Xss");
+
+    public JavaSoftwareProcessSshDriver(EntityLocal entity, SshMachineLocation 
machine) {
+        super(entity, machine);
+
+        entity.setAttribute(Attributes.LOG_FILE_LOCATION, 
getLogFileLocation());
+    }
+
+    protected abstract String getLogFileLocation();
+
+    public boolean isJmxEnabled() {
+        return (entity instanceof UsesJmx) && 
(entity.getConfig(UsesJmx.USE_JMX));
+    }
+
+    public boolean isJmxSslEnabled() {
+        return isJmxEnabled() && 
groovyTruth(entity.getConfig(UsesJmx.JMX_SSL_ENABLED));
+    }
+
+    /**
+     * Sets all JVM options (-X.. -D..) in an environment var JAVA_OPTS.
+     * <p>
+     * That variable is constructed from {@link #getJavaOpts()}, then wrapped 
_unescaped_ in double quotes. An
+     * error is thrown if there is an unescaped double quote in the string. 
All other unescaped
+     * characters are permitted, but unless $var expansion or `command` 
execution is desired (although
+     * this is not confirmed as supported) the generally caller should escape 
any such characters, for
+     * example using {@link 
BashStringEscapes#escapeLiteralForDoubleQuotedBash(String)}.
+     */
+    @Override
+    public Map<String, String> getShellEnvironment() {
+        List<String> javaOpts = getJavaOpts();
+
+        for (String it : javaOpts) {
+            BashStringEscapes.assertValidForDoubleQuotingInBash(it);
+        }
+        // do not double quote here; the env var is double quoted subsequently;
+        // spaces should be preceded by double-quote
+        // (if dbl quotes are needed we could pass on the command-line instead 
of in an env var)
+        String sJavaOpts = Joiner.on(' ').join(javaOpts);
+        return MutableMap.<String, 
String>builder().putAll(super.getShellEnvironment()).put("JAVA_OPTS", 
sJavaOpts).build();
+    }
+
+    /**
+     * arguments to pass to the JVM; this is the config options (e.g. 
-Xmx1024; only the contents of
+     * {@link #getCustomJavaConfigOptions()} by default) and java system 
properties (-Dk=v; add custom
+     * properties in {@link #getCustomJavaSystemProperties()})
+     * <p>
+     * See {@link #getShellEnvironment()} for discussion of quoting/escaping 
strategy.
+     **/
+    public List<String> getJavaOpts() {
+        Iterable<String> sysprops = 
Iterables.transform(getJavaSystemProperties().entrySet(),
+                new Function<Map.Entry<String, ?>, String>() {
+                    public String apply(Map.Entry<String, ?> entry) {
+                        String k = entry.getKey();
+                        Object v = entry.getValue();
+                        try {
+                            if (v != null && 
Primitives.isWrapperType(v.getClass())) {
+                                v = "" + v;
+                            } else {
+                                v = Tasks.resolveValue(v, Object.class, 
((EntityInternal)entity).getExecutionContext());
+                                if (v == null) {
+                                } else if (v instanceof CharSequence) {
+                                } else if 
(TypeCoercions.isPrimitiveOrBoxer(v.getClass())) {
+                                    v = "" + v;
+                                } else {
+                                    // could do toString, but that's likely 
not what is desired;
+                                    // probably a type mismatch,
+                                    // post-processing should be specified 
(common types are accepted
+                                    // above)
+                                    throw new IllegalArgumentException("cannot 
convert value " + v + " of type " + v.getClass()
+                                            + " to string to pass as JVM 
property; use a post-processor");
+                                }
+                            }
+                            return "-D" + k + (v != null ? "=" + v : "");
+                        } catch (Exception e) {
+                            log.warn("Error resolving java option key {}, 
propagating: {}", k, e);
+                            throw Throwables.propagate(e);
+                        }
+                    }
+                });
+
+        Set<String> result = MutableSet.<String> builder().
+                addAll(getJmxJavaConfigOptions()).
+                addAll(getCustomJavaConfigOptions()).
+                addAll(sysprops).
+            build();
+
+        for (String customOpt : entity.getConfig(UsesJava.JAVA_OPTS)) {
+            for (List<String> mutuallyExclusiveOpt : MUTUALLY_EXCLUSIVE_OPTS) {
+                if (mutuallyExclusiveOpt.contains(customOpt)) {
+                    result.removeAll(mutuallyExclusiveOpt);
+                }
+            }
+            for (String keyValOptPrefix : KEY_VAL_OPT_PREFIXES) {
+                if (customOpt.startsWith(keyValOptPrefix)) {
+                    for (Iterator<String> iter = result.iterator(); 
iter.hasNext();) {
+                        String existingOpt = iter.next();
+                        if (existingOpt.startsWith(keyValOptPrefix)) {
+                            iter.remove();
+                        }
+                    }
+                }
+            }
+            if (customOpt.contains("=")) {
+                String customOptPrefix = customOpt.substring(0, 
customOpt.indexOf("="));
+
+                for (Iterator<String> iter = result.iterator(); 
iter.hasNext();) {
+                    String existingOpt = iter.next();
+                    if (existingOpt.startsWith(customOptPrefix)) {
+                        iter.remove();
+                    }
+                }
+            }
+            result.add(customOpt);
+        }
+
+        return ImmutableList.copyOf(result);
+    }
+
+    /**
+     * Returns the complete set of Java system properties (-D defines) to set 
for the application.
+     * <p>
+     * This is exposed to the JVM as the contents of the {@code JAVA_OPTS} 
environment variable. Default
+     * set contains config key, custom system properties, and JMX defines.
+     * <p>
+     * Null value means to set -Dkey otherwise it is -Dkey=value.
+     * <p>
+     * See {@link #getShellEnvironment()} for discussion of quoting/escaping 
strategy.
+     */
+    protected Map<String,?> getJavaSystemProperties() {
+        return MutableMap.<String,Object>builder()
+                .putAll(getCustomJavaSystemProperties())
+                .putAll(isJmxEnabled() ? getJmxJavaSystemProperties() : 
Collections.<String,Object>emptyMap())
+                .putAll(entity.getConfig(UsesJava.JAVA_SYSPROPS))
+                .build();
+    }
+
+    /**
+     * Return extra Java system properties (-D defines) used by the 
application.
+     *
+     * Override as needed; default is an empty map.
+     */
+    protected Map getCustomJavaSystemProperties() {
+        return Maps.newLinkedHashMap();
+    }
+
+    /**
+     * Return extra Java config options, ie arguments starting with - which 
are passed to the JVM prior
+     * to the class name.
+     * <p>
+     * Note defines are handled separately, in {@link 
#getCustomJavaSystemProperties()}.
+     * <p>
+     * Override as needed; default is an empty list.
+     */
+    protected List<String> getCustomJavaConfigOptions() {
+        return Lists.newArrayList();
+    }
+
+    /** @deprecated since 0.6.0, the config key is always used instead of this 
*/ @Deprecated
+    public Integer getJmxPort() {
+        return !isJmxEnabled() ? Integer.valueOf(-1) : 
entity.getAttribute(UsesJmx.JMX_PORT);
+    }
+
+    /** @deprecated since 0.6.0, the config key is always used instead of this 
*/ @Deprecated
+    public Integer getRmiRegistryPort() {
+        return !isJmxEnabled() ? -1 : 
entity.getAttribute(UsesJmx.RMI_REGISTRY_PORT);
+    }
+
+    /** @deprecated since 0.6.0, the config key is always used instead of this 
*/ @Deprecated
+    public String getJmxContext() {
+        return !isJmxEnabled() ? null : 
entity.getAttribute(UsesJmx.JMX_CONTEXT);
+    }
+
+    /**
+     * Return the configuration properties required to enable JMX for a Java 
application.
+     *
+     * These should be set as properties in the {@code JAVA_OPTS} environment 
variable when calling the
+     * run script for the application.
+     */
+    protected Map<String, ?> getJmxJavaSystemProperties() {
+        MutableMap.Builder<String, Object> result = MutableMap.<String, 
Object> builder();
+
+        if (isJmxEnabled()) {
+            new JmxSupport(getEntity(), 
getRunDir()).applyJmxJavaSystemProperties(result);
+        }
+
+        return result.build();
+    }
+
+    /**
+     * Return any JVM arguments required, other than the -D defines returned 
by {@link #getJmxJavaSystemProperties()}
+     */
+    protected List<String> getJmxJavaConfigOptions() {
+        List<String> result = new ArrayList<String>();
+        if (isJmxEnabled()) {
+            result.addAll(new JmxSupport(getEntity(), 
getRunDir()).getJmxJavaConfigOptions());
+        }
+        return result;
+    }
+
+    /**
+     * Checks for the presence of Java on the entity's location, installing if 
necessary.
+     * @return true if the required version of Java was found on the machine 
or if it was installed correctly,
+     * otherwise false.
+     */
+    protected boolean checkForAndInstallJava(String requiredVersion) {
+        int requiredJavaMinor;
+        if (requiredVersion.contains(".")) {
+            List<String> requiredVersionParts = 
Splitter.on(".").splitToList(requiredVersion);
+            requiredJavaMinor = Integer.valueOf(requiredVersionParts.get(1));
+        } else if (requiredVersion.length() == 1) {
+            requiredJavaMinor = Integer.valueOf(requiredVersion);
+        } else {
+            log.error("java version required {} is not supported", 
requiredVersion);
+            throw new IllegalArgumentException("Required java version " + 
requiredVersion + " not supported");
+        }
+        Optional<String> installedJavaVersion = getInstalledJavaVersion();
+        if (installedJavaVersion.isPresent()) {
+            List<String> installedVersionParts = 
Splitter.on(".").splitToList(installedJavaVersion.get());
+            int javaMajor = Integer.valueOf(installedVersionParts.get(0));
+            int javaMinor = Integer.valueOf(installedVersionParts.get(1));
+            if (javaMajor == 1 && javaMinor >= requiredJavaMinor) {
+                log.debug("Java {} already installed at {}@{}", new 
Object[]{installedJavaVersion.get(), getEntity(), getLocation()});
+                return true;
+            }
+        }
+        return tryJavaInstall(requiredVersion, 
BashCommands.installJava(requiredJavaMinor)) == 0;
+    }
+
+    protected int tryJavaInstall(String version, String command) {
+        getLocation().acquireMutex("installing", "installing Java at " + 
getLocation());
+        try {
+            log.debug("Installing Java {} at {}@{}", new Object[]{version, 
getEntity(), getLocation()});
+            ProcessTaskFactory<Integer> taskFactory = 
SshTasks.newSshExecTaskFactory(getLocation(), command)
+                    .summary("install java ("+version+")")
+                    .configure(ShellTool.PROP_EXEC_ASYNC, true);
+            ProcessTaskWrapper<Integer> installCommand = 
Entities.submit(getEntity(), taskFactory);
+            int result = installCommand.get();
+            if (result != 0) {
+                log.warn("Installation of Java {} failed at {}@{}: {}",
+                        new Object[]{version, getEntity(), getLocation(), 
installCommand.getStderr()});
+            }
+            return result;
+        } finally {
+            getLocation().releaseMutex("installing");
+        }
+    }
+
+    /**
+    * @deprecated since 0.7.0; instead use {@link #getInstalledJavaVersion()}
+    */
+    @Deprecated
+    protected Optional<String> getCurrentJavaVersion() {
+        return getInstalledJavaVersion();
+    }
+
+    /**
+     * Checks for the version of Java installed on the entity's location over 
SSH.
+     * @return An Optional containing the version portion of `java -version`, 
or absent if no Java found.
+     */
+    protected Optional<String> getInstalledJavaVersion() {
+        log.debug("Checking Java version at {}@{}", getEntity(), 
getLocation());
+        // sed gets stdin like 'java version "1.7.0_45"'
+        ProcessTaskWrapper<Integer> versionCommand = 
Entities.submit(getEntity(), SshTasks.newSshExecTaskFactory(
+                getLocation(), "java -version 2>&1 | grep \" version\" | sed 
's/.*\"\\(.*\\).*\"/\\1/'"));
+        versionCommand.get();
+        String stdOut = versionCommand.getStdout().trim();
+        if (!Strings.isBlank(stdOut)) {
+            log.debug("Found Java version at {}@{}: {}", new Object[] 
{getEntity(), getLocation(), stdOut});
+            return Optional.of(stdOut);
+        } else {
+            log.debug("Found no Java installed at {}@{}", getEntity(), 
getLocation());
+            return Optional.absent();
+        }
+    }
+
+    /**
+     * Answers one of "OpenJDK", "Oracle", or other vendor info.
+     */
+    protected Optional<String> getCurrentJavaVendor() {
+        // TODO Also handle IBM jvm
+        log.debug("Checking Java vendor at {}@{}", getEntity(), getLocation());
+        ProcessTaskWrapper<Integer> versionCommand = 
Entities.submit(getEntity(), SshTasks.newSshExecTaskFactory(
+                getLocation(), "java -version 2>&1 | awk 'NR==2 {print $1}'"));
+        versionCommand.get();
+        String stdOut = versionCommand.getStdout().trim();
+        if (Strings.isBlank(stdOut)) {
+            log.debug("Found no Java installed at {}@{}", getEntity(), 
getLocation());
+            return Optional.absent();
+        } else if ("Java(TM)".equals(stdOut)) {
+            log.debug("Found Java version at {}@{}: {}", new Object[] 
{getEntity(), getLocation(), stdOut});
+            return Optional.of("Oracle");
+        } else {
+            return Optional.of(stdOut);
+        }
+    }
+
+    /**
+     * Checks for Java 6 or 7, installing Java 7 if neither are found. 
Override this method to
+     * check for and install specific versions of Java.
+     *
+     * @see #checkForAndInstallJava(String)
+     */
+    public boolean installJava() {
+        if (entity instanceof UsesJava) {
+            String version = entity.getConfig(UsesJava.JAVA_VERSION_REQUIRED);
+            return checkForAndInstallJava(version);
+        }
+        // by default it installs jdk7
+        return checkForAndInstallJava("1.7");
+    }
+
+    public void installJmxSupport() {
+        if (isJmxEnabled()) {
+            newScript("JMX_SETUP_PREINSTALL").body.append("mkdir -p 
"+getRunDir()).execute();
+            new JmxSupport(getEntity(), getRunDir()).install();
+        }
+    }
+
+    public void checkJavaHostnameBug() {
+        checkNoHostnameBug();
+
+        try {
+            ProcessTaskWrapper<Integer> hostnameTask = 
DynamicTasks.queue(SshEffectorTasks.ssh("echo FOREMARKER; hostname -f; echo 
AFTMARKER")).block();
+            String stdout = 
Strings.getFragmentBetween(hostnameTask.getStdout(), "FOREMARKER", "AFTMARKER");
+            if (hostnameTask.getExitCode() == 0 && Strings.isNonBlank(stdout)) 
{
+                String hostname = stdout.trim();
+                Integer len = hostname.length();
+                if (len > 63) {
+                    // likely to cause a java crash due to java bug 7089443 -- 
set a new short hostname
+                    // 
http://mail.openjdk.java.net/pipermail/net-dev/2012-July/004603.html
+                    String newHostname = 
"br-"+getEntity().getId().toLowerCase();
+                    log.info("Detected likelihood of Java hostname bug with 
hostname length "+len+" for "+getEntity()+"; renaming "+getMachine()+"  to 
hostname "+newHostname);
+                    
DynamicTasks.queue(SshEffectorTasks.ssh(BashCommands.setHostname(newHostname, 
null))).block();
+                }
+            } else {
+                log.debug("Hostname length could not be determined for 
location "+EffectorTasks.findSshMachine()+"; not doing Java hostname bug 
check");
+            }
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            log.warn("Error checking/fixing Java hostname bug (continuing): 
"+e, e);
+        }
+    }
+
+    @Override
+    public void setup() {
+        DynamicTasks.queue("install java", new Runnable() { public void run() {
+            installJava();
+        }});
+
+        // TODO check java version
+
+        if (getEntity().getConfig(UsesJava.CHECK_JAVA_HOSTNAME_BUG)) {
+            DynamicTasks.queue("check java hostname bug", new Runnable() { 
public void run() {
+                checkJavaHostnameBug(); }});
+        }
+    }
+
+    @Override
+    public void copyRuntimeResources() {
+        super.copyRuntimeResources();
+
+        if (isJmxEnabled()) {
+            DynamicTasks.queue("install jmx", new Runnable() { public void 
run() {
+                installJmxSupport(); }});
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxAttributeSensor.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxAttributeSensor.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxAttributeSensor.java
new file mode 100644
index 0000000..bee8126
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxAttributeSensor.java
@@ -0,0 +1,121 @@
+/*
+ * 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.brooklyn.entity.java;
+
+import java.util.concurrent.Callable;
+
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import org.apache.brooklyn.api.internal.EntityLocal;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.effector.core.AddSensor;
+import org.apache.brooklyn.sensor.core.DependentConfiguration;
+import org.apache.brooklyn.sensor.core.HttpRequestSensor;
+import org.apache.brooklyn.sensor.feed.jmx.JmxAttributePollConfig;
+import org.apache.brooklyn.sensor.feed.jmx.JmxFeed;
+import org.apache.brooklyn.sensor.feed.jmx.JmxHelper;
+import org.apache.brooklyn.sensor.ssh.SshCommandSensor;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.task.DynamicTasks;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Functions;
+import com.google.common.base.Preconditions;
+
+/**
+ * Configurable {@link org.apache.brooklyn.api.entity.EntityInitializer} which 
adds a JMX sensor feed to retrieve an
+ * <code>attribute</code> from a JMX <code>objectName</code>.
+ *
+ * @see SshCommandSensor
+ * @see HttpRequestSensor
+ */
+@Beta
+public final class JmxAttributeSensor<T> extends AddSensor<T> {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(JmxAttributeSensor.class);
+
+    public static final ConfigKey<String> OBJECT_NAME = 
ConfigKeys.newStringConfigKey("objectName", "JMX object name for sensor 
lookup");
+    public static final ConfigKey<String> ATTRIBUTE = 
ConfigKeys.newStringConfigKey("attribute", "JMX attribute to poll in object");
+    public static final ConfigKey<Object> DEFAULT_VALUE = 
ConfigKeys.newConfigKey(Object.class, "defaultValue", "Default value for 
sensor; normally null");
+
+    protected final String objectName;
+    protected final String attribute;
+    protected final Object defaultValue;
+
+    public JmxAttributeSensor(final ConfigBag params) {
+        super(params);
+ 
+        objectName = Preconditions.checkNotNull(params.get(OBJECT_NAME), 
"objectName");
+        attribute = Preconditions.checkNotNull(params.get(ATTRIBUTE), 
"attribute");
+        defaultValue = params.get(DEFAULT_VALUE);
+
+        try {
+            ObjectName.getInstance(objectName);
+        } catch (MalformedObjectNameException mone) {
+            throw new IllegalArgumentException("Malformed JMX object name: " + 
objectName, mone);
+        }
+    }
+
+    @Override
+    public void apply(final EntityLocal entity) {
+        super.apply(entity);
+
+        if (entity instanceof UsesJmx) {
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("Submitting task to add JMX sensor {} to {}", name, 
entity);
+            }
+
+            Task<Integer> jmxPortTask = 
DependentConfiguration.attributeWhenReady(entity, UsesJmx.JMX_PORT);
+            Task<JmxFeed> jmxFeedTask = Tasks.<JmxFeed>builder()
+                    .description("Add JMX feed")
+                    .body(new Callable<JmxFeed>() {
+                        @Override
+                        public JmxFeed call() throws Exception {
+                            JmxHelper helper = new JmxHelper(entity);
+                            Duration period = entity.getConfig(SENSOR_PERIOD);
+
+                            JmxFeed feed = JmxFeed.builder()
+                                    .entity(entity)
+                                    .period(period)
+                                    .helper(helper)
+                                    .pollAttribute(new 
JmxAttributePollConfig<T>(sensor)
+                                            .objectName(objectName)
+                                            .attributeName(attribute)
+                                            
.onFailureOrException(Functions.<T>constant((T) defaultValue)))
+                                    .build();
+                           return feed;
+                        }
+                    })
+                    .build();
+            DynamicTasks.submit(Tasks.sequential("Add JMX Sensor " + 
sensor.getName(), jmxPortTask, jmxFeedTask), entity);
+        } else {
+            throw new IllegalStateException(String.format("Entity %s does not 
support JMX", entity));
+        }
+
+        // TODO add entity shutdown hook to stop JmxFeed
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxSupport.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxSupport.java 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxSupport.java
new file mode 100644
index 0000000..5e67d30
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxSupport.java
@@ -0,0 +1,357 @@
+/*
+ * 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.brooklyn.entity.java;
+
+import java.util.EnumSet;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.internal.EntityLocal;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.config.ConfigKey.HasConfigKey;
+import org.apache.brooklyn.entity.core.EntityInternal;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.location.access.BrooklynAccessUtils;
+import org.apache.brooklyn.location.basic.Locations;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.sensor.feed.jmx.JmxHelper;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.BrooklynMavenArtifacts;
+import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.jmx.jmxmp.JmxmpAgent;
+import org.apache.brooklyn.util.jmx.jmxrmi.JmxRmiAgent;
+import org.apache.brooklyn.util.maven.MavenArtifact;
+import org.apache.brooklyn.util.maven.MavenRetriever;
+import org.apache.brooklyn.util.net.Urls;
+import org.apache.brooklyn.util.text.Strings;
+
+import com.google.common.base.Preconditions;
+import com.google.common.net.HostAndPort;
+
+public class JmxSupport implements UsesJmx {
+
+    private static final Logger log = 
LoggerFactory.getLogger(JmxSupport.class);
+
+    private final Entity entity;
+    private final String runDir;
+
+    private Boolean isJmx;
+    private Boolean isSecure;
+    private JmxAgentModes jmxAgentMode;
+
+    private static boolean warnedAboutNotOnClasspath = false;
+
+    /** run dir may be null if it is not accessed */
+    public JmxSupport(Entity entity, @Nullable String runDir) {
+        this.entity = Preconditions.checkNotNull(entity, "entity must be 
supplied");
+        this.runDir = runDir;
+    }
+
+    @Nonnull
+    public String getRunDir() {
+        return Preconditions.checkNotNull(runDir, "runDir must have been 
supplied to perform this operation");
+    }
+
+    public Entity getEntity() {
+        return entity;
+    }
+
+    <T> T getConfig(ConfigKey<T> key) {
+        return getEntity().getConfig(key);
+    }
+
+    <T> T getConfig(HasConfigKey<T> key) {
+        return getEntity().getConfig(key);
+    }
+
+    <T> void setConfig(ConfigKey<T> key, T value) {
+        ((EntityLocal)getEntity()).setConfig(key, value);
+    }
+
+    public Maybe<SshMachineLocation> getMachine() {
+        return Locations.findUniqueSshMachineLocation(entity.getLocations());
+    }
+
+    public boolean isJmx() {
+        init();
+        return isJmx;
+    }
+
+    public JmxAgentModes getJmxAgentMode() {
+        init();
+        if (jmxAgentMode==null) return JmxAgentModes.NONE;
+        return jmxAgentMode;
+    }
+
+    public boolean isSecure() {
+        init();
+        if (isSecure==null) return false;
+        return isSecure;
+    }
+
+    protected synchronized void init() {
+        if (isJmx!=null)
+            return;
+
+        if (Boolean.FALSE.equals(entity.getConfig(USE_JMX))) {
+            isJmx = false;
+            return;
+        }
+        isJmx = true;
+        jmxAgentMode = entity.getConfig(JMX_AGENT_MODE);
+        if (jmxAgentMode==null) jmxAgentMode = JmxAgentModes.AUTODETECT;
+
+        isSecure = entity.getConfig(JMX_SSL_ENABLED);
+        if (isSecure==null) isSecure = false;
+
+        if (jmxAgentMode==JmxAgentModes.AUTODETECT) {
+            if (isSecure()) {
+                jmxAgentMode = JmxAgentModes.JMXMP;
+            } else {
+                jmxAgentMode = JmxAgentModes.JMXMP_AND_RMI;
+                if 
(!ResourceUtils.create(this).doesUrlExist(getJmxAgentJarUrl())) {
+                    // can happen e.g. if eclipse build
+                    log.warn("JMX agent JAR not found 
("+getJmxAgentJarUrl()+") when auto-detecting JMX settings for "+entity+"; " +
+                            "likely cause is an incomplete build (e.g. from 
Eclipse; run a maven build then retry in the IDE); "+
+                            "reverting to NONE (use built-in Java JMX support, 
which will not go through firewalls)");
+                    jmxAgentMode = JmxAgentModes.NONE;
+                }
+            }
+
+            ((EntityLocal)entity).setConfig(JMX_AGENT_MODE, jmxAgentMode);
+        }
+
+        if (isSecure && jmxAgentMode!=JmxAgentModes.JMXMP) {
+            String msg = "JMX SSL is specified, but it requires JMXMP which is 
disabled, when configuring "+entity;
+            log.warn(msg);
+            throw new IllegalStateException(msg);
+        }
+    }
+
+    public void setJmxUrl() {
+        ((EntityInternal)entity).setAttribute(JMX_URL, getJmxUrl());
+    }
+
+    public String getJmxUrl() {
+        init();
+
+        HostAndPort jmx = 
BrooklynAccessUtils.getBrooklynAccessibleAddress(entity, 
entity.getAttribute(JMX_PORT));
+
+        if (EnumSet.of(JmxAgentModes.JMXMP, 
JmxAgentModes.JMXMP_AND_RMI).contains(getJmxAgentMode())) {
+            return JmxHelper.toJmxmpUrl(jmx.getHostText(), jmx.getPort());
+        } else {
+            if (getJmxAgentMode() == JmxAgentModes.NONE) {
+                fixPortsForModeNone();
+            }
+            // this will work for agent or agentless
+            HostAndPort rmi = 
BrooklynAccessUtils.getBrooklynAccessibleAddress(entity, 
entity.getAttribute(RMI_REGISTRY_PORT));
+            return JmxHelper.toRmiJmxUrl(jmx.getHostText(), jmx.getPort(), 
rmi.getPort(),
+                    entity.getAttribute(JMX_CONTEXT));
+        }
+    }
+
+    /** mode NONE cannot set a JMX (RMI server) port; it needs an RMI registry 
port,
+     * then gets redirected to an anonymous RMI server port;
+     * both the hostname and the anonymous port must be accessible to use this 
mode
+     * (hence the use of the other agents in most cases) */
+    protected int fixPortsForModeNone() {
+        assert getJmxAgentMode()==JmxAgentModes.NONE;
+        Integer jmxRemotePort = getEntity().getAttribute(JMX_PORT);
+        Integer rmiRegistryPort = getEntity().getAttribute(RMI_REGISTRY_PORT);
+        if (rmiRegistryPort!=null && rmiRegistryPort>0) {
+            if (jmxRemotePort==null || jmxRemotePort!=rmiRegistryPort) {
+                if (jmxRemotePort!=null && jmxRemotePort>0) {
+                    // ignore RMI registry port when mode 'none' is set -- set 
same as JMX port here
+                    // (bit irritating, but JMX_PORT will be ignored in this 
mode)
+                    log.warn("Ignoring JMX_PORT "+jmxRemotePort+" when 
configuring agentless JMX on "+getEntity()+"; will use RMI_REGISTRY_PORT 
"+rmiRegistryPort);
+                }
+                jmxRemotePort = rmiRegistryPort;
+                ((EntityLocal)getEntity()).setAttribute(JMX_PORT, 
jmxRemotePort);
+            }
+        } else {
+            if (jmxRemotePort==null || jmxRemotePort<=0) {
+                throw new IllegalStateException("Invalid JMX_PORT 
"+jmxRemotePort+" and RMI_REGISTRY_PORT "+rmiRegistryPort+" when configuring 
JMX "+getJmxAgentMode()+" on "+getEntity());
+            }
+            ((EntityLocal)getEntity()).setAttribute(RMI_REGISTRY_PORT, 
jmxRemotePort);
+        }
+        return jmxRemotePort;
+    }
+
+    public List<String> getJmxJavaConfigOptions() {
+        if (EnumSet.<JmxAgentModes>of(JmxAgentModes.NONE, 
JmxAgentModes.JMX_RMI).contains(getJmxAgentMode())) {
+            return MutableList.of();
+        } else {
+            return MutableList.of(String.format("-javaagent:%s", 
getJmxAgentJarDestinationFilePath()));
+        }
+    }
+
+    public String getJmxAgentJarDestinationFilePath() {
+        // cache the local path so we continue to work post-rebind to a 
different version
+        String result = getEntity().getAttribute(JMX_AGENT_LOCAL_PATH);
+        if (Strings.isNonBlank(result)) return result;
+        result = getJmxAgentJarDestinationFilePathDefault();
+        ((EntityInternal)getEntity()).setAttribute(JMX_AGENT_LOCAL_PATH, 
result);
+        return result;
+    }
+    
+    public String getJmxAgentJarDestinationFilePathDefault() {
+        return Urls.mergePaths(getRunDir(), getJmxAgentJarBasename());
+    }
+
+    @Nullable public MavenArtifact getJmxAgentJarMavenArtifact() {
+        switch (getJmxAgentMode()) {
+        case JMXMP:
+        case JMXMP_AND_RMI:
+            MavenArtifact result = BrooklynMavenArtifacts.artifact(null, 
"brooklyn-jmxmp-agent", "jar", "with-dependencies");
+            // the "with-dependencies" variant is needed; however the filename 
then has the classifier segment _replaced_ by "shaded" when this filename is 
created
+            result.setCustomFileNameAfterArtifactMarker("shaded");
+            result.setClassifierFileNameMarker("");
+            return result;
+        case JMX_RMI_CUSTOM_AGENT:
+            return BrooklynMavenArtifacts.jar("brooklyn-jmxrmi-agent");
+        default:
+            return null;
+        }
+    }
+
+    /** @deprecated since 0.6.0; use {@link #getJmxAgentJarMavenArtifact()} */
+    @Deprecated
+    public String getJmxAgentJarBasename() {
+        MavenArtifact artifact = getJmxAgentJarMavenArtifact();
+        if (artifact==null)
+            throw new IllegalStateException("Either JMX is not enabled or 
there is an error in the configuration (JMX mode "+getJmxAgentMode()+" does not 
support agent JAR)");
+        return artifact.getFilename();
+    }
+
+    /** returns URL for accessing the java agent, throwing if not applicable;
+     * prefers on classpath where it should be, but will fall back to taking 
from maven hosted
+     * (known problem in Eclipse where JARs are not always copied)
+     */
+    public String getJmxAgentJarUrl() {
+        MavenArtifact artifact = getJmxAgentJarMavenArtifact();
+        if (artifact==null)
+            throw new IllegalStateException("Either JMX is not enabled or 
there is an error in the configuration (JMX mode "+getJmxAgentMode()+" does not 
support agent JAR)");
+        String jar = "classpath://" + artifact.getFilename();
+        if (ResourceUtils.create(this).doesUrlExist(jar))
+            return jar;
+
+        String result = MavenRetriever.localUrl(artifact);
+        if (warnedAboutNotOnClasspath) {
+            log.debug("JMX JAR for "+artifact+" is not on the classpath; 
taking from "+result);
+        } else {
+            log.warn("JMX JAR for "+artifact+" is not on the classpath; taking 
from "+result+" (subsequent similar messages will be logged at debug)");
+            warnedAboutNotOnClasspath = true;
+        }
+        return result;
+    }
+
+    /** applies _some_ of the common settings needed to connect via JMX */
+    public void applyJmxJavaSystemProperties(MutableMap.Builder<String,Object> 
result) {
+        if (!isJmx()) return ;
+
+        Integer jmxPort = 
Preconditions.checkNotNull(entity.getAttribute(JMX_PORT), "jmx port must not be 
null for %s", entity);
+        HostAndPort jmx = 
BrooklynAccessUtils.getBrooklynAccessibleAddress(entity, jmxPort);
+        Integer jmxRemotePort = getEntity().getAttribute(JMX_PORT);
+        String hostName = jmx.getHostText();
+
+        result.put("com.sun.management.jmxremote", null);
+        result.put("java.rmi.server.hostname", hostName);
+
+        switch (getJmxAgentMode()) {
+        case JMXMP_AND_RMI:
+            Integer rmiRegistryPort = 
Preconditions.checkNotNull(entity.getAttribute(UsesJmx.RMI_REGISTRY_PORT), 
"registry port (config val %s)", entity.getConfig(UsesJmx.RMI_REGISTRY_PORT));
+            result.put(JmxmpAgent.RMI_REGISTRY_PORT_PROPERTY, rmiRegistryPort);
+        case JMXMP:
+            if (jmxRemotePort==null || jmxRemotePort<=0)
+                throw new IllegalStateException("Unsupported JMX port 
"+jmxRemotePort+" - when applying system properties ("+getJmxAgentMode()+" / 
"+getEntity()+")");
+            result.put(JmxmpAgent.JMXMP_PORT_PROPERTY, jmxRemotePort);
+            // with JMXMP don't try to tell it the hostname -- it isn't needed 
for JMXMP, and if specified
+            // it will break if the hostname we see is not known at the 
server, e.g. a forwarding public IP
+            result.remove("java.rmi.server.hostname");
+            break;
+        case JMX_RMI_CUSTOM_AGENT:
+            if (jmxRemotePort==null || jmxRemotePort<=0)
+                throw new IllegalStateException("Unsupported JMX port 
"+jmxRemotePort+" - when applying system properties ("+getJmxAgentMode()+" / 
"+getEntity()+")");
+            result.put(JmxRmiAgent.RMI_REGISTRY_PORT_PROPERTY, 
Preconditions.checkNotNull(entity.getAttribute(UsesJmx.RMI_REGISTRY_PORT), 
"registry port"));
+            result.put(JmxRmiAgent.JMX_SERVER_PORT_PROPERTY, jmxRemotePort);
+            break;
+        case NONE:
+            jmxRemotePort = fixPortsForModeNone();
+        case JMX_RMI:
+            result.put("com.sun.management.jmxremote.port", jmxRemotePort);
+            result.put("java.rmi.server.useLocalHostname", "true");
+            break;
+        default:
+            throw new IllegalStateException("Unsupported JMX mode - when 
applying system properties ("+getJmxAgentMode()+" / "+getEntity()+")");
+        }
+
+        if (isSecure()) {
+            // set values true, and apply keys pointing to keystore / 
truststore
+            getJmxSslSupport().applyAgentJmxJavaSystemProperties(result);
+        } else {
+            result.
+                put("com.sun.management.jmxremote.ssl", false).
+                put("com.sun.management.jmxremote.authenticate", false);
+        }
+    }
+
+    /** installs files needed for JMX, to the runDir given in constructor, 
assuming the runDir has been created */
+    public void install() {
+        if (EnumSet.of(JmxAgentModes.JMXMP_AND_RMI, JmxAgentModes.JMXMP, 
JmxAgentModes.JMX_RMI_CUSTOM_AGENT).contains(getJmxAgentMode())) {
+            Tasks.setBlockingDetails("Copying JMX agent jar to server.");
+            try {
+                
getMachine().get().copyTo(ResourceUtils.create(this).getResourceFromUrl(
+                        getJmxAgentJarUrl()), 
getJmxAgentJarDestinationFilePath());
+            } finally {
+                Tasks.resetBlockingDetails();
+            }
+        }
+        if (isSecure()) {
+            getJmxSslSupport().install();
+        }
+    }
+
+    protected JmxmpSslSupport getJmxSslSupport() {
+        return new JmxmpSslSupport(this);
+    }
+
+    /** sets JMR_RMI_CUSTOM_AGENT as the connection mode for the indicated 
apps.
+     * <p>
+     * TODO callers of this method have RMI dependencies in the actual app;
+     * we should look at removing them, so that those pieces of software can 
run behind
+     * forwarding public IP's and over SSL (both reasons JMXMP is preferred by 
us!)
+     */
+    public void recommendJmxRmiCustomAgent() {
+        // set JMX_RMI because the registry is needed (i think)
+        Maybe<Object> jmx = entity.getConfigRaw(UsesJmx.JMX_AGENT_MODE, true);
+        if (!jmx.isPresentAndNonNull()) {
+            setConfig(UsesJmx.JMX_AGENT_MODE, 
JmxAgentModes.JMX_RMI_CUSTOM_AGENT);
+        } else if (jmx.get()!=JmxAgentModes.JMX_RMI_CUSTOM_AGENT) {
+            log.warn("Entity "+entity+" may not function unless running 
JMX_RMI_CUSTOM_AGENT mode (asked to use "+jmx.get()+")");
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxmpSslSupport.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxmpSslSupport.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxmpSslSupport.java
new file mode 100644
index 0000000..36d1773
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxmpSslSupport.java
@@ -0,0 +1,134 @@
+/*
+ * 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.brooklyn.entity.java;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+
+import org.apache.brooklyn.util.collections.MutableMap.Builder;
+import org.apache.brooklyn.util.core.crypto.FluentKeySigner;
+import org.apache.brooklyn.util.core.crypto.SecureKeys;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.jmx.jmxmp.JmxmpAgent;
+import org.apache.brooklyn.util.net.Urls;
+
+import com.google.common.base.Preconditions;
+
+public class JmxmpSslSupport {
+
+    final static String BROOKLYN_VERSION = "0.8.0-SNAPSHOT";  // 
BROOKLYN_VERSION (updated by script)
+    
+    private final JmxSupport jmxSupport;
+    
+    private KeyStore agentTrustStore;
+    private KeyStore agentKeyStore;
+    
+    public JmxmpSslSupport(JmxSupport jmxSupport) {
+        this.jmxSupport = Preconditions.checkNotNull(jmxSupport);
+    }
+    
+    public String getJmxSslKeyStoreFilePath() {
+        return Urls.mergePaths(jmxSupport.getRunDir(), "jmx-keystore");
+    }
+    
+    public String getJmxSslTrustStoreFilePath() {
+        return Urls.mergePaths(jmxSupport.getRunDir(), "jmx-truststore");
+    }
+    
+    public void applyAgentJmxJavaSystemProperties(Builder<String, Object> 
result) {
+        result.
+            put(JmxmpAgent.USE_SSL_PROPERTY, true).
+            put(JmxmpAgent.AUTHENTICATE_CLIENTS_PROPERTY, true).
+            // the option below wants a jmxremote.password file; we use certs 
(above) to authenticate
+            put("com.sun.management.jmxremote.authenticate", false);
+
+        result.
+            put(JmxmpAgent.JMXMP_KEYSTORE_FILE_PROPERTY, 
getJmxSslKeyStoreFilePath()).
+            put(JmxmpAgent.JMXMP_TRUSTSTORE_FILE_PROPERTY, 
getJmxSslTrustStoreFilePath());
+    }
+
+    public FluentKeySigner getBrooklynRootSigner() {
+        // TODO use brooklyn root CA keys etc
+        return new FluentKeySigner("brooklyn-root");
+    }
+
+    /** builds remote keystores, stores config keys/certs, and copies 
necessary files across */
+    public void install() {
+        try {
+            // build truststore and keystore
+            FluentKeySigner signer = getBrooklynRootSigner();
+            KeyPair jmxAgentKey = SecureKeys.newKeyPair();
+            X509Certificate jmxAgentCert = 
signer.newCertificateFor("jmxmp-agent", jmxAgentKey);
+
+            agentKeyStore = SecureKeys.newKeyStore();
+            agentKeyStore.setKeyEntry("jmxmp-agent", jmxAgentKey.getPrivate(), 
+                    // TODO jmx.ssl.agent.keyPassword
+                    "".toCharArray(),
+                    new Certificate[] { jmxAgentCert });
+            ByteArrayOutputStream agentKeyStoreBytes = new 
ByteArrayOutputStream();
+            agentKeyStore.store(agentKeyStoreBytes, 
+                    // TODO jmx.ssl.agent.keyStorePassword
+                    "".toCharArray());
+            
+            agentTrustStore = SecureKeys.newKeyStore();
+            agentTrustStore.setCertificateEntry("brooklyn", 
getJmxAccessCert());
+            ByteArrayOutputStream agentTrustStoreBytes = new 
ByteArrayOutputStream();
+            agentTrustStore.store(agentTrustStoreBytes, "".toCharArray());
+            
+            // install the truststore and keystore and rely on JmxSupport to 
install the agent
+            Tasks.setBlockingDetails("Copying keystore and truststore to the 
server.");
+            try {
+                jmxSupport.getMachine().get().copyTo(new 
ByteArrayInputStream(agentKeyStoreBytes.toByteArray()), 
getJmxSslKeyStoreFilePath());
+                jmxSupport.getMachine().get().copyTo(new 
ByteArrayInputStream(agentTrustStoreBytes.toByteArray()), 
getJmxSslTrustStoreFilePath());
+            } finally {
+                Tasks.resetBlockingDetails();
+            }
+
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    public synchronized Certificate getJmxAccessCert() {
+        Certificate cert = jmxSupport.getConfig(UsesJmx.JMX_SSL_ACCESS_CERT);
+        if (cert!=null) return cert;
+        // TODO load from keyStoreUrl
+        KeyPair jmxAccessKey = SecureKeys.newKeyPair();
+        X509Certificate jmxAccessCert = 
getBrooklynRootSigner().newCertificateFor("brooklyn-jmx-access", jmxAccessKey);
+
+        jmxSupport.setConfig(UsesJmx.JMX_SSL_ACCESS_CERT, jmxAccessCert);
+        jmxSupport.setConfig(UsesJmx.JMX_SSL_ACCESS_KEY, 
jmxAccessKey.getPrivate());
+        
+        return jmxAccessCert;
+    }
+    
+    public synchronized PrivateKey getJmxAccessKey() {
+        PrivateKey key = jmxSupport.getConfig(UsesJmx.JMX_SSL_ACCESS_KEY);
+        if (key!=null) return key;
+        getJmxAccessCert();
+        return jmxSupport.getConfig(UsesJmx.JMX_SSL_ACCESS_KEY);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/java/UsesJava.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/java/UsesJava.java 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/UsesJava.java
new file mode 100644
index 0000000..1f80eda
--- /dev/null
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/java/UsesJava.java
@@ -0,0 +1,68 @@
+/*
+ * 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.brooklyn.entity.java;
+
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.config.MapConfigKey;
+import org.apache.brooklyn.core.config.SetConfigKey;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+
+public interface UsesJava {
+
+    /** system properties (-D) to append to JAVA_OPTS; normally accessed 
through {@link JavaEntityMethods#javaSysProp(String)} */
+    @SetFromFlag("javaSysProps")
+    public static final MapConfigKey<String> JAVA_SYSPROPS = new 
MapConfigKey<String>(String.class,
+            "java.sysprops", "Java command line system properties", 
Maps.<String,String>newLinkedHashMap());
+
+    /**
+     * Used to set java options. These options are prepended to the defaults.
+     * They can also be used to override defaults. The rules for overrides are:
+     * <ul>
+     *   <li>If contains a mutually exclusive setting, then the others are 
removed. Those supported are:
+     *     <ul>
+     *       <li>"-client" and "-server"
+     *     </ul>
+     *   <li>If value has a well-known prefix indicating it's a key-value 
pair. Those supported are:
+     *     <ul>
+     *       <li>"-Xmx"
+     *       <li>"-Xms"
+     *       <li>"-Xss"
+     *     </ul>
+     *   <li>If value contains "=" then see if there's a default that matches 
the section up to the "=".
+     *       If there is, then remove the original and just include this.
+     *       e.g. "-XX:MaxPermSize=512m" could be overridden in this way.
+     * </ul>
+     */
+    @SetFromFlag("javaOpts")
+    public static final SetConfigKey<String> JAVA_OPTS = new 
SetConfigKey<String>(String.class,
+            "java.opts", "Java command line options", 
ImmutableSet.<String>of());
+
+    public static final ConfigKey<Boolean> CHECK_JAVA_HOSTNAME_BUG = 
ConfigKeys.newBooleanConfigKey(
+            "java.check.hostname.bug", "Check whether hostname is too long and 
will likely crash Java" +
+                    "due to bug 7089443", true);
+
+    @SetFromFlag("javaVersionRequired")
+    ConfigKey<String> JAVA_VERSION_REQUIRED = 
ConfigKeys.newStringConfigKey("java.version.required", "Java version required", 
"1.7");
+
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/java/UsesJavaMXBeans.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/java/UsesJavaMXBeans.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/UsesJavaMXBeans.java
new file mode 100644
index 0000000..4f2d20b
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/UsesJavaMXBeans.java
@@ -0,0 +1,77 @@
+/*
+ * 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.brooklyn.entity.java;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.sensor.core.BasicAttributeSensor;
+import org.apache.brooklyn.sensor.core.Sensors;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+
+public interface UsesJavaMXBeans {
+
+    @SetFromFlag("mxbeanStatsEnabled")
+    ConfigKey<Boolean> MXBEAN_STATS_ENABLED =
+            ConfigKeys.newBooleanConfigKey("java.metrics.mxbeanStatsEnabled", 
"Enables collection of JVM stats from the MXBeans, such as memory and thread 
usage (default is true)", true);
+
+    AttributeSensor<Long> USED_HEAP_MEMORY =
+            Sensors.newLongSensor("java.metrics.heap.used", "Current heap size 
(bytes)");
+    AttributeSensor<Long> INIT_HEAP_MEMORY =
+            Sensors.newLongSensor("java.metrics.heap.init", "Initial heap size 
(bytes)");
+    AttributeSensor<Long> COMMITTED_HEAP_MEMORY =
+            Sensors.newLongSensor("java.metrics.heap.committed", "Commited 
heap size (bytes)");
+    AttributeSensor<Long> MAX_HEAP_MEMORY =
+            Sensors.newLongSensor("java.metrics.heap.max", "Max heap size 
(bytes)");
+    AttributeSensor<Long> NON_HEAP_MEMORY_USAGE =
+            Sensors.newLongSensor("java.metrics.nonheap.used", "Current 
non-heap size (bytes)");
+    AttributeSensor<Integer> CURRENT_THREAD_COUNT =
+            Sensors.newIntegerSensor( "java.metrics.threads.current", "Current 
number of threads");
+    AttributeSensor<Integer> PEAK_THREAD_COUNT =
+            Sensors.newIntegerSensor("java.metrics.threads.max", "Peak number 
of threads");
+
+    // runtime system attributes
+    AttributeSensor<Long> START_TIME =
+            Sensors.newLongSensor("java.metrics.starttime", "Start time of 
Java process (UTC)");
+    AttributeSensor<Long> UP_TIME =
+            Sensors.newLongSensor("java.metrics.uptime", "Uptime of Java 
process (millis, elapsed since start)");
+    
+    //operating system attributes
+    AttributeSensor<Double> PROCESS_CPU_TIME = Sensors.newDoubleSensor( 
+            "java.metrics.processCpuTime.total", "Process CPU time (total 
millis since start)");
+    AttributeSensor<Double> PROCESS_CPU_TIME_FRACTION_LAST = 
Sensors.newDoubleSensor( 
+            "java.metrics.processCpuTime.fraction.last", "Fraction of CPU time 
used, reported by JVM (percentage, last datapoint)");
+    AttributeSensor<Double> PROCESS_CPU_TIME_FRACTION_IN_WINDOW = 
Sensors.newDoubleSensor( 
+            "java.metrics.processCpuTime.fraction.windowed", "Fraction of CPU 
time used, reported by JVM (percentage, over time window)");
+    
+    AttributeSensor<Integer> AVAILABLE_PROCESSORS =
+            Sensors.newIntegerSensor("java.metrics.processors.available", 
"number of processors available to the Java virtual machine");
+    AttributeSensor<Double> SYSTEM_LOAD_AVERAGE
+            = Sensors.newDoubleSensor("java.metrics.systemload.average", 
"average system load");
+    AttributeSensor<Long> TOTAL_PHYSICAL_MEMORY_SIZE =
+            Sensors.newLongSensor("java.metrics.physicalmemory.total", "The 
physical memory available to the operating system");
+    AttributeSensor<Long> FREE_PHYSICAL_MEMORY_SIZE =
+            Sensors.newLongSensor("java.metrics.physicalmemory.free", "The 
free memory available to the operating system");
+
+    // GC attributes
+    AttributeSensor<Map> GARBAGE_COLLECTION_TIME = new 
BasicAttributeSensor<Map>(Map.class, "java.metrics.gc.time", "garbage 
collection time");
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/java/UsesJmx.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/java/UsesJmx.java 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/UsesJmx.java
new file mode 100644
index 0000000..4831826
--- /dev/null
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/java/UsesJmx.java
@@ -0,0 +1,190 @@
+/*
+ * 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.brooklyn.entity.java;
+
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.PortRange;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.BasicConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.location.basic.PortRanges;
+import org.apache.brooklyn.sensor.core.AttributeSensorAndConfigKey;
+import org.apache.brooklyn.sensor.core.BasicAttributeSensorAndConfigKey;
+import org.apache.brooklyn.sensor.core.PortAttributeSensorAndConfigKey;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+
+public interface UsesJmx extends UsesJava {
+
+    public static final int DEFAULT_JMX_PORT = 1099; // RMI port?
+
+    @SetFromFlag("useJmx")
+    ConfigKey<Boolean> USE_JMX = ConfigKeys.newConfigKey("jmx.enabled", "JMX 
enabled", Boolean.TRUE);
+
+    /** Chosen by Java itself by default, setting this will only have any 
effect if using an agent. */
+    @SetFromFlag("jmxPort")
+    PortAttributeSensorAndConfigKey JMX_PORT = new 
PortAttributeSensorAndConfigKey(
+        "jmx.direct.port", "JMX direct/private port (e.g. JMX RMI server port, 
or JMXMP port, but not RMI registry port)", PortRanges.fromString("31001+"));
+    
+    // Default is deliberately null for this unused config; if we used 
"31001+" then we'd potentially give this sensor 
+    // the value 31001 and jmx.direct.port the value 31002. See 
https://issues.apache.org/jira/browse/BROOKLYN-98
+    /** @deprecated since 0.7.0, kept for rebinding with the anonymous class; 
code should only ever use {@link #JMX_PORT} */ @Deprecated
+    PortAttributeSensorAndConfigKey JMX_PORT_LEGACY = new 
PortAttributeSensorAndConfigKey(
+            "jmx.direct.port.legacy.NOT_USED", "Legacy definition JMX 
direct/private port (e.g. JMX RMI server port, or JMXMP port, but not RMI 
registry port)", null) {
+        private static final long serialVersionUID = 3846846080809179437L;
+        @Override protected Integer convertConfigToSensor(PortRange value, 
Entity entity) {
+            // TODO when using JmxAgentModes.NONE we should *not* convert, but 
leave it null
+            // (e.g. to prevent a warning in e.g. ActiveMQIntegrationTest)
+            // [there was - previously - a note about needing to move these 
keys to UsesJmx,
+            // that has been done, so not sure if there is anything more 
needed or if we can just
+            // check here entity.getConfig(JMX_AGENT_MODE) ... needs testing 
of course]
+            return super.convertConfigToSensor(value, entity);
+        }
+    };
+    
+    /** Well-known port used by Java itself to start the RMI registry where 
JMX private port can be discovered, ignored if using JMXMP agent. */
+    @SetFromFlag("rmiRegistryPort")
+    PortAttributeSensorAndConfigKey RMI_REGISTRY_PORT = 
ConfigKeys.newPortSensorAndConfigKey(
+            "rmi.registry.port", "RMI registry port, used for discovering JMX 
(private) port", PortRanges.fromString("1099,19099+"));
+
+    @SetFromFlag("jmxContext")
+    AttributeSensorAndConfigKey<String, String> JMX_CONTEXT = 
ConfigKeys.newStringSensorAndConfigKey("jmx.context", "JMX context path", 
"jmxrmi");
+
+    AttributeSensor<String> JMX_URL = new 
BasicAttributeSensorAndConfigKey<String>(
+            String.class, "jmx.service.url", "The URL for connecting to the 
MBean Server");
+
+    /** Forces JMX to be secured, using JMXMP so it gets through firewalls 
<em>and</em> SSL/TLS. */
+    @SetFromFlag("jmxSecure")
+    ConfigKey<Boolean> JMX_SSL_ENABLED = 
ConfigKeys.newBooleanConfigKey("jmx.ssl.enabled", "JMX over JMXMP enabled with 
SSL/TLS", Boolean.FALSE);
+
+    enum JmxAgentModes {
+        /** Auto-detect the agent to use based on location. Prefer {@link 
#JMXMP} except at localhost which uses {@link #JMX_RMI_CUSTOM_AGENT}. */
+        AUTODETECT,
+
+        /** JMXMP which permits firewall access through a single port {@link 
UsesJmx#JMX_PORT}. */
+        JMXMP,
+
+        /** Start {@link #JMXMP} along with an RMI Registry on {@link 
UsesJmx#RMI_REGISTRY_PORT}, redirecting to an anonymous high-numbered port as 
the RMI server. */
+        JMXMP_AND_RMI,
+
+        /** JMX over RMI custom agent which permits access through a known 
{@link UsesJmx#RMI_REGISTRY_PORT}, redirected to a known {@link 
UsesJmx#JMX_PORT}.
+         * Both ports must be opened on the firewall, and the same hostname 
resolvable on the target machine and by the client */
+        JMX_RMI_CUSTOM_AGENT,
+
+        /** As with {@link UsesJmx#JMX_RMI_CUSTOM_AGENT} but no custom agent 
requred, entity must handle pots correctly.  */
+        JMX_RMI,
+
+        /** Do not install a JMX agent. Use the default {@link 
UsesJmx#RMI_REGISTRY_PORT}, redirected to an unknown port for JMX. */
+        NONE
+    }
+
+    @SetFromFlag("jmxAgentMode")
+    ConfigKey<JmxAgentModes> JMX_AGENT_MODE = 
ConfigKeys.newConfigKey("jmx.agent.mode",
+            "What type of JMX agent to use; defaults to null (autodetect) 
which means " +
+            "JMXMP_AND_RMI allowing firewall access through a single port as 
well as local access supporting jconsole " +
+            "(unless JMX_SSL_ENABLED is set, in which case it is JMXMP only)",
+            JmxAgentModes.AUTODETECT);
+
+    /* Currently these are only used to connect, so only applies where systems 
set this up themselves. */
+    AttributeSensorAndConfigKey<String, String> JMX_USER = 
ConfigKeys.newStringSensorAndConfigKey("jmx.user", "JMX username");
+    AttributeSensorAndConfigKey<String, String> JMX_PASSWORD = 
ConfigKeys.newStringSensorAndConfigKey("jmx.password", "JMX password");
+    
+    AttributeSensorAndConfigKey<String, String> JMX_AGENT_LOCAL_PATH = 
ConfigKeys.newStringSensorAndConfigKey("jmx.agent.local.path", "Path to JMX 
driver on the local machine");
+
+    /*
+     * Synopsis of how the keys work for JMX_SSL:
+     *
+     * BROOKLYN
+     *  * brooklyn ROOT key + cert ->
+     *      used to identify things brooklyn has signed, ie to confirm their 
identity
+     *      signs all certs created by brooklyn
+     *      (created per entity if not specified as input)
+     *  * brooklyn JMX ACCESS key + cert ->
+     *      used to authenticate brooklyn to remote JMX agent
+     *      typically, but not necessarily, signed by ROOT cert
+     *      (typically created per entity, unless specified;
+     *      global would probably be fine but more work;
+     *      however it is important that this _not_ sign agents keys,
+     *      to prevent agents from accessing other agents)
+     *
+     * AGENT (e.g. JMX server in each managed java process)
+     *  * gets AGENT key + cert ->
+     *      signed by brooklyn ROOT, used to authenticate itself to brooklyn
+     *      (brooklyn trusts this; does not need to remember this)
+     *  * trusts only the relevant brooklyn JMX ACCESS key (its truststore 
contains that cert)
+     */
+
+    /* TODO brooklyn ROOT key
+     *
+    public static final ConfigKey<String> BROOKLYN_SSL_ROOT_KEYSTORE_URL = new 
BasicConfigKey<String>(
+            String.class, "brooklyn.ssl.root.keyStoreUrl", "URL to keystore 
Brooklyn should use as root private key and certificate-signing authority", 
null);
+
+    public static final ConfigKey<String> BROOKLYN_SSL_ROOT_KEY_DATA = new 
BasicConfigKey<String>(
+            String.class, "brooklyn.ssl.root.key", "root private key (RSA 
string format), used to sign managed servers", null);
+    public static final ConfigKey<String> BROOKLYN_SSL_ROOT_CERT_DATA = new 
BasicConfigKey<String>(
+            String.class, "brooklyn.ssl.root.cert", "certificate for root 
private key (RSA string format)", null);
+
+     * brooklyn.ssl.root.keyStorePassword
+     * brooklyn.ssl.root.keyAlias (if null, looks for one called 'brooklyn', 
otherwise takes the first key)
+     * brooklyn.ssl.root.keyPassword
+     */
+
+    public static final ConfigKey<PrivateKey> JMX_SSL_ACCESS_KEY = new 
BasicConfigKey<PrivateKey>(
+            PrivateKey.class, "jmx.ssl.access.key", "key used to access a JMX 
agent (typically per entity, embedded in the managed JVM)", null);
+    public static final ConfigKey<Certificate> JMX_SSL_ACCESS_CERT = new 
BasicConfigKey<Certificate>(
+            Certificate.class, "jmx.ssl.access.cert", "certificate of key used 
to access a JMX agent", null);
+
+    /* TODO specify a keystore from which to get the access key
+     * (above keys are set programmatically, typically _not_ by the user ... 
keystore would be the way to do that)
+     *
+     * jmx.ssl.access.keyStoreUrl (optional)
+     * jmx.ssl.access.keyStorePassword (optional)
+     * jmx.ssl.access.keyAlias (optional)
+     */
+
+    /* could allow user to specify additional certs for JMX agents which 
should be trusted
+     *
+     * jmx.ssl.access.trustStoreUrl
+     */
+
+    /* optionally: could allow JMX agent to trust additional accessers,
+     * and/or use known keys in the case that other accessers might want to 
authenticate the JMX server
+     *
+     * NB currently agent keys are not stored in brooklyn... no reason to as
+     * (a) currently we trust jmx agents; and (b) for agent-auth we should 
simply sign keys;
+     * either way, seems fine for brooklyn to throw them away once they are 
installed on the remote machine)
+     *
+     * jmx.ssl.agent.keyStoreUrl
+     * jmx.ssl.agent.keyStorePassword
+     * jmx.ssl.agent.keyAlias
+     * jmx.ssl.agent.keyPassword
+     *
+     * jmx.ssl.agent.trustStoreUrl
+     */
+
+    /* optionally: this could be set to disallow attaching to JMX through the 
attach mechanism
+     * (but this option is generally not considered needed, as JVM attachment 
is
+     * already restricted to localhost and to the the user running the process)
+     *
+     * -XX:+DisableAttachMechanism
+     */
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/java/VanillaJavaApp.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/java/VanillaJavaApp.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/VanillaJavaApp.java
new file mode 100644
index 0000000..9319406
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/VanillaJavaApp.java
@@ -0,0 +1,77 @@
+/*
+ * 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.brooklyn.entity.java;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+import org.apache.brooklyn.sensor.core.Sensors;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+@ImplementedBy(VanillaJavaAppImpl.class)
+public interface VanillaJavaApp extends SoftwareProcess, UsesJava, UsesJmx, 
UsesJavaMXBeans {
+
+    // TODO Make jmxPollPeriod @SetFromFlag easier to use: currently a 
confusion over long and TimeDuration, and 
+    // no ability to set default value (can't just set field because config 
vals read/set in super-constructor :-(
+
+    @SetFromFlag("args")
+    ConfigKey<List> ARGS = ConfigKeys.newConfigKey(List.class,
+            "vanillaJavaApp.args", "Arguments for launching the java app", 
Lists.newArrayList());
+    
+    @SetFromFlag(value="main", nullable=false)
+    ConfigKey<String> MAIN_CLASS = 
ConfigKeys.newStringConfigKey("vanillaJavaApp.mainClass", "class to launch");
+
+    @SetFromFlag("classpath")
+    ConfigKey<List> CLASSPATH = ConfigKeys.newConfigKey(List.class,
+            "vanillaJavaApp.classpath", "classpath to use, as list of URL 
entries; " +
+            "these URLs are copied to lib/ and expanded in the case of 
tar/tgz/zip",
+            Lists.newArrayList());
+
+    AttributeSensor<List> CLASSPATH_FILES = Sensors.newSensor(List.class,
+            "vanillaJavaApp.classpathFiles", "classpath used, list of files");
+
+    @SetFromFlag("jvmXArgs")
+    ConfigKey<List> JVM_XARGS = ConfigKeys.newConfigKey(List.class,
+            "vanillaJavaApp.jvmXArgs", "JVM -X args for the java app (e.g. 
memory)", 
+            MutableList.of("-Xms128m", "-Xmx512m", "-XX:MaxPermSize=512m"));
+
+    @SetFromFlag("jvmDefines")
+    ConfigKey<Map> JVM_DEFINES = ConfigKeys.newConfigKey(Map.class,
+            "vanillaJavaApp.jvmDefines", "JVM system property definitions for 
the app",
+            Maps.newLinkedHashMap());
+
+    public String getMainClass();
+    public List<String> getClasspath();
+    public List<String> getClasspathFiles();
+    public Map getJvmDefines();
+    public List getJvmXArgs();
+    public String getRunDir();
+
+    public void kill();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/java/VanillaJavaAppDriver.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/java/VanillaJavaAppDriver.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/VanillaJavaAppDriver.java
new file mode 100644
index 0000000..fd8c624
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/VanillaJavaAppDriver.java
@@ -0,0 +1,26 @@
+/*
+ * 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.brooklyn.entity.java;
+
+/**
+ * The {@link JavaSoftwareProcessDriver} for a {@link VanillaJavaApp}.
+ */
+public interface VanillaJavaAppDriver extends JavaSoftwareProcessDriver {
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/java/VanillaJavaAppImpl.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/java/VanillaJavaAppImpl.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/VanillaJavaAppImpl.java
new file mode 100644
index 0000000..a81de86
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/VanillaJavaAppImpl.java
@@ -0,0 +1,112 @@
+/*
+ * 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.brooklyn.entity.java;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.entity.software.base.SoftwareProcessImpl;
+import org.apache.brooklyn.sensor.feed.jmx.JmxFeed;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+
+public class VanillaJavaAppImpl extends SoftwareProcessImpl implements 
VanillaJavaApp {
+
+    static {
+        JavaAppUtils.init();
+    }
+
+    private static final Logger log = 
LoggerFactory.getLogger(VanillaJavaApp.class);
+
+    @SetFromFlag
+    protected long jmxPollPeriod;
+
+    protected JmxFeed jmxFeed;
+
+    public VanillaJavaAppImpl() {}
+
+    @VisibleForTesting
+    public VanillaJavaAppImpl(Map<?,?> properties, Entity parent) {
+        super(properties, parent);
+    }
+
+    public String getMainClass() { return getConfig(MAIN_CLASS); }
+    public List<String> getClasspath() { return getConfig(CLASSPATH); }
+    public List<String> getClasspathFiles() { return 
getAttribute(CLASSPATH_FILES); }
+    public Map getJvmDefines() { return getConfig(JVM_DEFINES); }
+    public List getJvmXArgs() { return getConfig(JVM_XARGS); }
+
+    public void addToClasspath(String url) {
+        List<String> cp = getConfig(CLASSPATH);
+        List<String> newCP = new ArrayList<String>();
+        if (cp!=null) newCP.addAll(cp);
+        newCP.add(url);
+        setConfig(CLASSPATH, newCP);
+    }
+
+    public void addToClasspath(Collection<String> urls) {
+        List<String> cp = getConfig(CLASSPATH);
+        List<String> newCP = new ArrayList<String>();
+        if (cp!=null) newCP.addAll(cp);
+        newCP.addAll(urls);
+        setConfig(CLASSPATH, newCP);
+    }
+
+    @Override
+    protected void connectSensors() {
+        super.connectSensors();
+
+        if (((VanillaJavaAppDriver) getDriver()).isJmxEnabled()) {
+            jmxPollPeriod = (jmxPollPeriod > 0) ? jmxPollPeriod : 3000;
+            jmxFeed = JavaAppUtils.connectMXBeanSensors(this, jmxPollPeriod);
+        }
+
+        connectServiceUpIsRunning();
+    }
+
+    @Override
+    public void disconnectSensors() {
+        super.disconnectSensors();
+        disconnectServiceUpIsRunning();
+        if (jmxFeed != null) jmxFeed.stop();
+    }
+
+    @Override
+    public Class<? extends VanillaJavaAppDriver> getDriverInterface() {
+        return VanillaJavaAppDriver.class;
+    }
+
+    public String getRunDir() {
+        // FIXME Make this an attribute; don't assume it hsa to be ssh? What 
uses this?
+        VanillaJavaAppSshDriver driver = (VanillaJavaAppSshDriver) getDriver();
+        return (driver != null) ? driver.getRunDir() : null;
+    }
+
+    @Override
+    public void kill() {
+        getDriver().kill();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/java/VanillaJavaAppSshDriver.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/java/VanillaJavaAppSshDriver.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/VanillaJavaAppSshDriver.java
new file mode 100644
index 0000000..011f4ff
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/VanillaJavaAppSshDriver.java
@@ -0,0 +1,211 @@
+/*
+ * 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.brooklyn.entity.java;
+
+import static java.lang.String.format;
+
+import java.io.File;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.entity.software.base.lifecycle.ScriptHelper;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.file.ArchiveBuilder;
+import org.apache.brooklyn.util.core.file.ArchiveUtils;
+import org.apache.brooklyn.util.core.internal.ssh.SshTool;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.net.Urls;
+import org.apache.brooklyn.util.os.Os;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+/**
+ * The SSH implementation of the {@link VanillaJavaAppDriver}.
+ */
+public class VanillaJavaAppSshDriver extends JavaSoftwareProcessSshDriver 
implements VanillaJavaAppDriver {
+
+    // FIXME this should be a config, either on the entity or -- probably 
better -- 
+    // an alternative / override timeout on the SshTool for file copy commands
+    final static int NUM_RETRIES_FOR_COPYING = 4;
+    
+    public VanillaJavaAppSshDriver(VanillaJavaAppImpl entity, 
SshMachineLocation machine) {
+        super(entity, machine);
+    }
+
+    @Override
+    public VanillaJavaAppImpl getEntity() {
+        return (VanillaJavaAppImpl) super.getEntity();
+    }
+
+    @Override
+    protected String getLogFileLocation() {
+        return Os.mergePathsUnix(getRunDir(), "console");
+    }
+
+    @Override
+    public void install() {
+        newScript(INSTALLING).execute();
+    }
+
+    @Override
+    public void customize() {
+        newScript(CUSTOMIZING)
+                .body.append(format("mkdir -p %s/lib", getRunDir()))
+                .failOnNonZeroResultCode()
+                .execute();
+
+        SshMachineLocation machine = getMachine();
+        VanillaJavaApp entity = getEntity();
+        for (String entry : entity.getClasspath()) {
+            // If a local folder, then create archive from contents first
+            if (Urls.isDirectory(entry)) {
+                File jarFile = ArchiveBuilder.jar().addDirContentsAt(new 
File(entry), "").create();
+                entry = jarFile.getAbsolutePath();
+            }
+
+            // Determine filename
+            String destFile = entry.contains("?") ? entry.substring(0, 
entry.indexOf('?')) : entry;
+            destFile = destFile.substring(destFile.lastIndexOf('/') + 1);
+
+            ArchiveUtils.deploy(MutableMap.<String, Object>of(), entry, 
machine, getRunDir(), Os.mergePaths(getRunDir(), "lib"), destFile);
+        }
+
+        ScriptHelper helper = newScript(CUSTOMIZING+"-classpath")
+                .body.append(String.format("ls -1 \"%s\"", 
Os.mergePaths(getRunDir(), "lib")))
+                .gatherOutput();
+        helper.setFlag(SshTool.PROP_NO_EXTRA_OUTPUT, true);
+        int result = helper.execute();
+        if (result != 0) {
+            throw new IllegalStateException("Error listing classpath files: " 
+ helper.getResultStderr());
+        }
+        String stdout = helper.getResultStdout();
+
+        // Transform stdout into list of files in classpath
+        if (Strings.isBlank(stdout)) {
+            getEntity().setAttribute(VanillaJavaApp.CLASSPATH_FILES, 
ImmutableList.of(Os.mergePaths(getRunDir(), "lib")));
+        } else {
+            // FIXME Cannot handle spaces in paths properly
+            Iterable<String> lines = 
Splitter.on(CharMatcher.BREAKING_WHITESPACE).omitEmptyStrings().trimResults().split(stdout);
+            Iterable<String> files = Iterables.transform(lines, new 
Function<String, String>() {
+                        @Override
+                        public String apply(@Nullable String input) {
+                            return Os.mergePathsUnix(getRunDir(), "lib", 
input);
+                        }
+                    });
+            getEntity().setAttribute(VanillaJavaApp.CLASSPATH_FILES, 
ImmutableList.copyOf(files));
+        }
+    }
+
+    public String getClasspath() {
+        @SuppressWarnings("unchecked")
+        List<String> files = 
getEntity().getAttribute(VanillaJavaApp.CLASSPATH_FILES);
+        if (files == null || files.isEmpty()) {
+            return null;
+        } else {
+            return Joiner.on(":").join(files);
+        }
+    }
+
+    @Override
+    public void launch() {
+        String clazz = getEntity().getMainClass();
+        String args = getArgs();
+
+        newScript(MutableMap.of(USE_PID_FILE, true), LAUNCHING)
+            .body.append(
+                    format("echo \"launching: java $JAVA_OPTS %s %s\"", clazz, 
args),
+                    format("java $JAVA_OPTS -cp \"%s\" %s %s >> %s/console 
2>&1 </dev/null &", getClasspath(), clazz, args, getRunDir())
+                )
+            .execute();
+    }
+
+    public String getArgs(){
+        List<Object> args = getEntity().getConfig(VanillaJavaApp.ARGS);
+        StringBuilder sb = new StringBuilder();
+        Iterator<Object> it = args.iterator();
+        while (it.hasNext()) {
+            Object argO = it.next();
+            try {
+                String arg = Tasks.resolveValue(argO, String.class, 
getEntity().getExecutionContext());
+                BashStringEscapes.assertValidForDoubleQuotingInBash(arg);
+                sb.append(format("\"%s\"",arg));
+            } catch (Exception e) {
+                throw Exceptions.propagate(e);
+            }
+            if (it.hasNext()) {
+                sb.append(" ");
+            }
+        }
+
+        return sb.toString();
+    }
+
+    @Override
+    public boolean isRunning() {
+        int result = newScript(MutableMap.of(USE_PID_FILE, true), 
CHECK_RUNNING).execute();
+        return result == 0;
+    }
+
+    @Override
+    public void stop() {
+        newScript(MutableMap.of(USE_PID_FILE, true), STOPPING).execute();
+    }
+
+    @Override
+    public void kill() {
+        newScript(MutableMap.of(USE_PID_FILE, true), KILLING).execute();
+    }
+
+    @Override
+    protected Map getCustomJavaSystemProperties() {
+        return MutableMap.builder()
+                .putAll(super.getCustomJavaSystemProperties())
+                .putAll(getEntity().getJvmDefines())
+                .build();
+    }
+
+    @Override
+    protected List<String> getCustomJavaConfigOptions() {
+        return MutableList.<String>builder()
+                .addAll(super.getCustomJavaConfigOptions())
+                .addAll(getEntity().getJvmXArgs())
+                .build();
+    }
+
+    @Override
+    public Map<String,String> getShellEnvironment() {
+        return MutableMap.<String,String>builder()
+                .putAll(super.getShellEnvironment())
+                .putIfNotNull("CLASSPATH", getClasspath())
+                .build();
+    }
+}


Reply via email to