http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefConfigs.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefConfigs.java 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefConfigs.java
new file mode 100644
index 0000000..f572b1f
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefConfigs.java
@@ -0,0 +1,102 @@
+/*
+ * 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.chef;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.MapConfigKey.MapModifications;
+import org.apache.brooklyn.core.config.SetConfigKey.SetModifications;
+import org.apache.brooklyn.entity.core.EntityInternal;
+import org.apache.brooklyn.util.git.GithubUrls;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+
+/** Conveniences for configuring brooklyn Chef entities 
+ * @since 0.6.0 */
+@Beta
+public class ChefConfigs {
+
+    public static void addToLaunchRunList(EntitySpec<?> entity, String 
...recipes) {
+        for (String recipe: recipes)
+            entity.configure(ChefConfig.CHEF_LAUNCH_RUN_LIST, 
SetModifications.addItem(recipe));
+    }
+
+    public static void addToLaunchRunList(EntityInternal entity, String 
...recipes) {
+        for (String recipe: recipes)
+            entity.setConfig(ChefConfig.CHEF_LAUNCH_RUN_LIST, 
SetModifications.addItem(recipe));
+    }
+
+    public static void addToCookbooksFromGithub(EntitySpec<?> entity, String 
...cookbookNames) {
+        for (String cookbookName: cookbookNames)
+            addToCookbooksFromGithub(entity, cookbookName, 
getGithubOpscodeRepo(cookbookName)); 
+    }
+    
+    public static void addToCookbooksFromGithub(EntityInternal entity, String 
...cookbookNames) {
+        for (String cookbookName: cookbookNames)
+            addToCookbooksFromGithub(entity, cookbookName, 
getGithubOpscodeRepo(cookbookName)); 
+    }
+
+    public static String getGithubOpscodeRepo(String cookbookName) {
+        return getGithubOpscodeRepo(cookbookName, "master");
+    }
+    public static String getGithubOpscodeRepo(String cookbookName, String tag) 
{
+        return GithubUrls.tgz("opscode-cookbooks", cookbookName, tag);
+    }
+    
+    public static void addToCookbooksFromGithub(EntitySpec<?> entity, String 
cookbookName, String cookbookUrl) {
+        entity.configure(ChefConfig.CHEF_COOKBOOK_URLS.subKey(cookbookName), 
cookbookUrl);
+    }
+
+    public static void addToCookbooksFromGithub(EntityInternal entity, String 
cookbookName, String cookbookUrl) {
+        entity.setConfig(ChefConfig.CHEF_COOKBOOK_URLS.subKey(cookbookName), 
cookbookUrl);
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static void addLaunchAttributes(EntitySpec<?> entity, Map<? extends 
Object,? extends Object> attributesMap) {
+        entity.configure(ChefConfig.CHEF_LAUNCH_ATTRIBUTES, 
MapModifications.add((Map)attributesMap));
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static void addLaunchAttributes(EntityInternal entity, Map<? 
extends Object,? extends Object> attributesMap) {
+        entity.setConfig(ChefConfig.CHEF_LAUNCH_ATTRIBUTES, 
MapModifications.add((Map)attributesMap));
+    }
+    
+    /** replaces the attributes underneath the rootAttribute parameter with 
the given value;
+     * see {@link #addLaunchAttributesMap(EntitySpec, Map)} for richer 
functionality */
+    public static void setLaunchAttribute(EntitySpec<?> entity, String 
rootAttribute, Object value) {
+        
entity.configure(ChefConfig.CHEF_LAUNCH_ATTRIBUTES.subKey(rootAttribute), 
value);
+    }
+    
+    /** replaces the attributes underneath the rootAttribute parameter with 
the given value;
+     * see {@link #addLaunchAttributesMap(EntitySpec, Map)} for richer 
functionality */
+    public static void setLaunchAttribute(EntityInternal entity, String 
rootAttribute, Object value) {
+        
entity.setConfig(ChefConfig.CHEF_LAUNCH_ATTRIBUTES.subKey(rootAttribute), 
value);
+    }
+
+    public static <T> T getRequiredConfig(Entity entity, ConfigKey<T> key) {
+        return Preconditions.checkNotNull(
+                Preconditions.checkNotNull(entity, "Entity must be 
supplied").getConfig(key), 
+                "Key "+key+" is required on "+entity);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefEntity.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefEntity.java 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefEntity.java
new file mode 100644
index 0000000..1b1f866
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefEntity.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.chef;
+
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+
+@ImplementedBy(ChefEntityImpl.class)
+public interface ChefEntity extends SoftwareProcess, ChefConfig {
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefEntityImpl.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefEntityImpl.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefEntityImpl.java
new file mode 100644
index 0000000..911b546
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefEntityImpl.java
@@ -0,0 +1,38 @@
+/*
+ * 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.chef;
+
+import org.apache.brooklyn.entity.stock.EffectorStartableImpl;
+import org.apache.brooklyn.util.text.Strings;
+
+public class ChefEntityImpl extends EffectorStartableImpl implements 
ChefEntity {
+
+    public void init() {
+        String primaryName = getConfig(CHEF_COOKBOOK_PRIMARY_NAME);
+        if (!Strings.isBlank(primaryName)) setDefaultDisplayName(primaryName+" 
(chef)");
+        
+        super.init();
+        new ChefLifecycleEffectorTasks().attachLifecycleEffectors(this);
+    }
+
+    @Override
+    public void populateServiceNotUpDiagnostics() {
+        // TODO no-op currently; should check ssh'able etc
+    }    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefLifecycleEffectorTasks.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefLifecycleEffectorTasks.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefLifecycleEffectorTasks.java
new file mode 100644
index 0000000..e4cbf1c
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefLifecycleEffectorTasks.java
@@ -0,0 +1,360 @@
+/*
+ * 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.chef;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.entity.core.Attributes;
+import org.apache.brooklyn.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+import 
org.apache.brooklyn.entity.software.base.lifecycle.MachineLifecycleEffectorTasks;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.location.basic.Machines;
+import org.apache.brooklyn.sensor.ssh.SshEffectorTasks;
+import org.apache.brooklyn.util.collections.Jsonya;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.collections.Jsonya.Navigator;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.task.DynamicTasks;
+import org.apache.brooklyn.util.core.task.TaskTags;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.net.Urls;
+import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.time.Time;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+
+/** 
+ * Creates effectors to start, restart, and stop processes using Chef.
+ * <p>
+ * Instances of this should use the {@link ChefConfig} config attributes to 
configure startup,
+ * and invoke {@link #usePidFile(String)} or {@link #useService(String)} to 
determine check-running and stop behaviour.
+ * Alternatively this can be subclassed and {@link #postStartCustom()} and 
{@link #stopProcessesAtMachine()} overridden.
+ * 
+ * @since 0.6.0
+ **/
+@Beta
+public class ChefLifecycleEffectorTasks extends MachineLifecycleEffectorTasks 
implements ChefConfig {
+
+    private static final Logger log = 
LoggerFactory.getLogger(ChefLifecycleEffectorTasks.class);
+    
+    protected String _pidFile, _serviceName, _windowsServiceName;
+    
+    public ChefLifecycleEffectorTasks() {
+    }
+    
+    public ChefLifecycleEffectorTasks usePidFile(String pidFile) {
+        this._pidFile = pidFile;
+        return this;
+    }
+    public ChefLifecycleEffectorTasks useService(String serviceName) {
+        this._serviceName = serviceName;
+        return this;
+    }
+    public ChefLifecycleEffectorTasks useWindowsService(String serviceName) {
+        this._windowsServiceName = serviceName;
+        return this;
+    }
+    
+    public String getPidFile() {
+        if (_pidFile!=null) return _pidFile;
+        return _pidFile = entity().getConfig(ChefConfig.PID_FILE);
+    }
+
+    public String getServiceName() {
+        if (_serviceName!=null) return _serviceName;
+        return _serviceName = entity().getConfig(ChefConfig.SERVICE_NAME);
+    }
+
+    protected String getNodeName() {
+        // (node name is needed so we can node delete it)
+        
+        // TODO would be better if CHEF_NODE_NAME were a freemarker template, 
could access entity.id, or hostname, etc,
+        // in addition to supporting hard-coded node names (which is all we 
support so far).
+        
+        String nodeName = entity().getConfig(ChefConfig.CHEF_NODE_NAME);
+        if (Strings.isNonBlank(nodeName)) return 
Strings.makeValidFilename(nodeName);
+        // node name is taken from ID of this entity, if not specified
+        return entity().getId();
+    }
+
+    public String getWindowsServiceName() {
+        if (_windowsServiceName!=null) return _windowsServiceName;
+        return _windowsServiceName = 
entity().getConfig(ChefConfig.WINDOWS_SERVICE_NAME);
+    }
+
+    @Override
+    public void attachLifecycleEffectors(Entity entity) {
+        if (getPidFile()==null && getServiceName()==null && 
getClass().equals(ChefLifecycleEffectorTasks.class)) {
+            // warn on incorrect usage
+            log.warn("Uses of "+getClass()+" must define a PID file or a 
service name (or subclass and override {start,stop} methods as per javadoc) " +
+                    "in order for check-running and stop to work");
+        }
+            
+        super.attachLifecycleEffectors(entity);
+    }
+
+    public static ChefModes detectChefMode(Entity entity) {
+        ChefModes mode = entity.getConfig(ChefConfig.CHEF_MODE);
+        if (mode == ChefModes.AUTODETECT) {
+            // TODO server via API
+            ProcessTaskWrapper<Boolean> installCheck = DynamicTasks.queue(
+                    ChefServerTasks.isKnifeInstalled());
+            mode = installCheck.get() ? ChefModes.KNIFE : ChefModes.SOLO;
+            log.debug("Using Chef in "+mode+" mode due to autodetect exit code 
"+installCheck.getExitCode());
+        }
+        Preconditions.checkNotNull(mode, "Non-null "+ChefConfig.CHEF_MODE+" 
required for "+entity);
+        return mode;
+    }
+    
+    @Override
+    protected String startProcessesAtMachine(Supplier<MachineLocation> 
machineS) {
+        ChefModes mode = detectChefMode(entity());
+        switch (mode) {
+        case KNIFE:
+            startWithKnifeAsync();
+            break;
+            
+        case SOLO:
+            startWithChefSoloAsync();
+            break;
+            
+        default:
+            throw new IllegalStateException("Unknown Chef mode "+mode+" when 
starting processes for "+entity());
+        }
+        
+        return "chef start tasks submitted ("+mode+")";
+    }
+
+    protected String getPrimaryCookbook() {
+        return entity().getConfig(CHEF_COOKBOOK_PRIMARY_NAME);
+    }
+    
+    @SuppressWarnings({ "unchecked", "deprecation" })
+    protected void startWithChefSoloAsync() {
+        String baseDir = 
MachineLifecycleEffectorTasks.resolveOnBoxDir(entity(), 
Machines.findUniqueSshMachineLocation(entity().getLocations()).get());
+        String installDir = Urls.mergePaths(baseDir, "installs/chef");
+        
+        @SuppressWarnings("rawtypes")
+        Map<String, String> cookbooks = (Map) 
+            ConfigBag.newInstance( entity().getConfig(CHEF_COOKBOOK_URLS) )
+            .putIfAbsent( entity().getConfig(CHEF_COOKBOOKS) )
+            .getAllConfig();
+        if (cookbooks.isEmpty())
+            log.warn("No cookbook_urls set for "+entity()+"; launch will 
likely fail subsequently");
+        DynamicTasks.queue(
+                ChefSoloTasks.installChef(installDir, false), 
+                ChefSoloTasks.installCookbooks(installDir, cookbooks, false));
+
+        // TODO chef for and run a prestart recipe if necessary
+        // TODO open ports
+
+        String primary = getPrimaryCookbook();
+        
+        // put all config under brooklyn/cookbook/config
+        Navigator<MutableMap<Object, Object>> attrs = 
Jsonya.newInstancePrimitive().at("brooklyn");
+        if (Strings.isNonBlank(primary)) attrs.at(primary);
+        attrs.at("config");
+        attrs.put( entity().getAllConfigBag().getAllConfig() );
+        // and put launch attrs at root
+        try {
+            
attrs.root().put((Map<?,?>)Tasks.resolveDeepValue(entity().getConfig(CHEF_LAUNCH_ATTRIBUTES),
 Object.class, entity().getExecutionContext()));
+        } catch (Exception e) { Exceptions.propagate(e); }
+        
+        Collection<? extends String> runList = 
entity().getConfig(CHEF_LAUNCH_RUN_LIST);
+        if (runList==null) runList = entity().getConfig(CHEF_RUN_LIST);
+        if (runList==null) {
+            if (Strings.isNonBlank(primary)) runList = 
ImmutableList.of(primary+"::"+"start");
+            else throw new IllegalStateException("Require a primary cookbook 
or a run_list to effect "+"start"+" on "+entity());
+        }
+        
+        String runDir = Urls.mergePaths(baseDir,  
+            
"apps/"+entity().getApplicationId()+"/chef/entities/"+entity().getEntityType().getSimpleName()+"_"+entity().getId());
+        
+        DynamicTasks.queue(ChefSoloTasks.buildChefFile(runDir, installDir, 
"launch", 
+                runList, (Map<String, Object>) attrs.root().get()));
+        
+        DynamicTasks.queue(ChefSoloTasks.runChef(runDir, "launch", 
entity().getConfig(CHEF_RUN_CONVERGE_TWICE)));
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes", "deprecation" })
+    protected void startWithKnifeAsync() {
+        // TODO prestart, ports (as above); also, note, some aspects of this 
are untested as we need a chef server
+        
+        String primary = getPrimaryCookbook();
+
+        // put all config under brooklyn/cookbook/config
+        Navigator<MutableMap<Object, Object>> attrs = 
Jsonya.newInstancePrimitive().at("brooklyn");
+        if (Strings.isNonBlank(primary)) attrs.at(primary);
+        attrs.at("config");
+        attrs.put( entity().getAllConfigBag().getAllConfig() );
+        // and put launch attrs at root
+        try {
+            
attrs.root().put((Map<?,?>)Tasks.resolveDeepValue(entity().getConfig(CHEF_LAUNCH_ATTRIBUTES),
 Object.class, entity().getExecutionContext()));
+        } catch (Exception e) { Exceptions.propagate(e); }
+
+        Collection<? extends String> runList = 
entity().getConfig(CHEF_LAUNCH_RUN_LIST);
+        if (runList==null) runList = entity().getConfig(CHEF_RUN_LIST);
+        if (runList==null) {
+            if (Strings.isNonBlank(primary)) runList = 
ImmutableList.of(primary+"::"+"start");
+            else throw new IllegalStateException("Require a primary cookbook 
or a run_list to effect "+"start"+" on "+entity());
+        }
+
+        DynamicTasks.queue(
+                ChefServerTasks.knifeConvergeTask()
+                    .knifeNodeName(getNodeName())
+                    .knifeRunList(Strings.join(runList, ","))
+                    .knifeAddAttributes((Map<? extends Object, ? extends 
Object>)(Map) attrs.root().get())
+                    
.knifeRunTwice(entity().getConfig(CHEF_RUN_CONVERGE_TWICE)) );
+    }
+
+    protected void postStartCustom() {
+        boolean result = false;
+        result |= tryCheckStartPid();
+        result |= tryCheckStartService();
+        result |= tryCheckStartWindowsService();
+        if (!result) {
+            log.warn("No way to check whether "+entity()+" is running; 
assuming yes");
+        }
+        entity().setAttribute(SoftwareProcess.SERVICE_UP, true);
+    }
+    
+    protected boolean tryCheckStartPid() {
+        if (getPidFile()==null) return false;
+        
+        // if it's still up after 5s assume we are good (default behaviour)
+        Time.sleep(Duration.FIVE_SECONDS);
+        if 
(!DynamicTasks.queue(SshEffectorTasks.isPidFromFileRunning(getPidFile()).runAsRoot()).get())
 {
+            throw new IllegalStateException("The process for "+entity()+" 
appears not to be running (pid file "+getPidFile()+")");
+        }
+
+        // and set the PID
+        entity().setAttribute(Attributes.PID, 
+                Integer.parseInt(DynamicTasks.queue(SshEffectorTasks.ssh("cat 
"+getPidFile()).runAsRoot()).block().getStdout().trim()));
+        return true;
+    }
+
+    protected boolean tryCheckStartService() {
+        if (getServiceName()==null) return false;
+        
+        // if it's still up after 5s assume we are good (default behaviour)
+        Time.sleep(Duration.FIVE_SECONDS);
+        if 
(!((Integer)0).equals(DynamicTasks.queue(SshEffectorTasks.ssh("/etc/init.d/"+getServiceName()+"
 status").runAsRoot()).get())) {
+            throw new IllegalStateException("The process for "+entity()+" 
appears not to be running (service "+getServiceName()+")");
+        }
+
+        return true;
+    }
+
+    protected boolean tryCheckStartWindowsService() {
+        if (getWindowsServiceName()==null) return false;
+        
+        // if it's still up after 5s assume we are good (default behaviour)
+        Time.sleep(Duration.FIVE_SECONDS);
+        if (!((Integer)0).equals(DynamicTasks.queue(SshEffectorTasks.ssh("sc 
query \""+getWindowsServiceName()+"\" | find 
\"RUNNING\"").runAsCommand()).get())) {
+            throw new IllegalStateException("The process for "+entity()+" 
appears not to be running (windowsService "+getWindowsServiceName()+")");
+        }
+
+        return true;
+    }
+
+    @Override
+    protected String stopProcessesAtMachine() {
+        boolean result = false;
+        result |= tryStopService();
+        result |= tryStopWindowsService();
+        result |= tryStopPid();
+        if (!result) {
+            throw new IllegalStateException("The process for "+entity()+" 
could not be stopped (no impl!)");
+        }
+        return "stopped";
+    }
+    
+    @Override
+    protected StopMachineDetails<Integer> stopAnyProvisionedMachines() {
+        if (detectChefMode(entity())==ChefModes.KNIFE) {
+            DynamicTasks.queue(
+                // if this task fails show it as failed but don't block 
subsequent routines
+                // (ie allow us to actually decommission the machine)
+                // TODO args could be a List<String> config key ?
+                TaskTags.markInessential(
+                new KnifeTaskFactory<String>("delete node and client 
registration at chef server")
+                    .add("knife node delete "+getNodeName()+" -y")
+                    .add("knife client delete "+getNodeName()+" -y")
+                    .requiringZeroAndReturningStdout()
+                    .newTask() ));
+        }
+
+        return super.stopAnyProvisionedMachines();
+    }
+    
+    protected boolean tryStopService() {
+        if (getServiceName()==null) return false;
+        int result = 
DynamicTasks.queue(SshEffectorTasks.ssh("/etc/init.d/"+getServiceName()+" 
stop").runAsRoot()).get();
+        if (0==result) return true;
+        if 
(entity().getAttribute(Attributes.SERVICE_STATE_ACTUAL)!=Lifecycle.RUNNING)
+            return true;
+        
+        throw new IllegalStateException("The process for "+entity()+" appears 
could not be stopped (exit code "+result+" to service stop)");
+    }
+
+    protected boolean tryStopWindowsService() {
+        if (getWindowsServiceName()==null) return false;
+                int result = DynamicTasks.queue(SshEffectorTasks.ssh("sc query 
\""+getWindowsServiceName()+"\"").runAsCommand()).get();
+        if (0==result) return true;
+        if 
(entity().getAttribute(Attributes.SERVICE_STATE_ACTUAL)!=Lifecycle.RUNNING)
+            return true;
+
+        throw new IllegalStateException("The process for "+entity()+" appears 
could not be stopped (exit code "+result+" to service stop)");
+    }
+
+    protected boolean tryStopPid() {
+        Integer pid = entity().getAttribute(Attributes.PID);
+        if (pid==null) {
+            if 
(entity().getAttribute(Attributes.SERVICE_STATE_ACTUAL)==Lifecycle.RUNNING && 
getPidFile()==null)
+                log.warn("No PID recorded for "+entity()+" when running, with 
PID file "+getPidFile()+"; skipping kill in "+Tasks.current());
+            else 
+                if (log.isDebugEnabled())
+                    log.debug("No PID recorded for "+entity()+"; skipping 
("+entity().getAttribute(Attributes.SERVICE_STATE_ACTUAL)+" / 
"+getPidFile()+")");
+            return false;
+        }
+        
+        // allow non-zero exit as process may have already been killed
+        DynamicTasks.queue(SshEffectorTasks.ssh(
+                "kill "+pid, "sleep 5", BashCommands.ok("kill -9 
"+pid)).allowingNonZeroExitCode().runAsRoot()).block();
+        
+        if 
(DynamicTasks.queue(SshEffectorTasks.isPidRunning(pid).runAsRoot()).get()) {
+            throw new IllegalStateException("Process for "+entity()+" in 
"+pid+" still running after kill");
+        }
+        entity().setAttribute(Attributes.PID, null);
+        return true;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefServerTasks.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefServerTasks.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefServerTasks.java
new file mode 100644
index 0000000..d8132be
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefServerTasks.java
@@ -0,0 +1,97 @@
+/*
+ * 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.chef;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.security.KeyPair;
+
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.util.core.crypto.SecureKeys;
+
+import com.google.common.base.Throwables;
+import com.google.common.io.Files;
+
+public class ChefServerTasks {
+
+    private static File chefKeyDir;
+    
+    private synchronized static File getExtractedKeysDir() {
+        if (chefKeyDir==null) {
+            chefKeyDir = Files.createTempDir();
+            chefKeyDir.deleteOnExit();
+        }
+        return chefKeyDir;
+    }
+    
+    /** extract key to a temp file, but one per machine, scheduled for 
deletion afterwards;
+     * we extract the key because chef has no way to accept passphrases at 
present */
+    synchronized static File extractKeyFile(SshMachineLocation machine) {
+        File f = new File(getExtractedKeysDir(), 
machine.getAddress().getHostName()+".pem");
+        if (f.exists()) return f;
+        KeyPair data = machine.findKeyPair();
+        if (data==null) return null;
+        try {
+            f.deleteOnExit();
+            Files.write(SecureKeys.stringPem(data), f, 
Charset.defaultCharset());
+            return f;
+        } catch (IOException e) {
+            throw Throwables.propagate(e);
+        }
+    }
+    
+    public static KnifeTaskFactory<Boolean> isKnifeInstalled() {
+        return new KnifeTaskFactory<Boolean>("knife install check")
+                .knifeAddParameters("node list")
+                .notThrowingOnCommonKnifeErrors()
+                .returningIsExitCodeZero();
+    }
+
+    /** plain knife converge task - run list must be set, other arguments are 
optional */
+    public static KnifeConvergeTaskFactory<String> knifeConvergeTask() {
+        return new KnifeConvergeTaskFactory<String>("knife converge")
+                .requiringZeroAndReturningStdout();
+    }
+    /** knife converge task configured for this run list (and sudo) */
+    public static KnifeConvergeTaskFactory<String> knifeConvergeRunList(String 
runList) {
+        return knifeConvergeTask()
+                .knifeRunList(runList)
+                .knifeSudo(true);
+    }
+    
+    /** knife converge task configured for this run list on windows (ssh) */
+    public static KnifeConvergeTaskFactory<String> 
knifeConvergeRunListWindowsSsh(String runList) {
+        return knifeConvergeTask()
+                .knifeRunList(runList)
+                .knifeSudo(false)
+                .knifeAddExtraBootstrapParameters("windows ssh");
+    }
+    
+    /** knife converge task configured for this run list on windows (winrm) */
+    public static KnifeConvergeTaskFactory<String> 
knifeConvergeRunListWindowsWinrm(String runList) {
+        return knifeConvergeTask()
+                .knifeRunList(runList)
+                .knifeSudo(false)
+                .knifeAddExtraBootstrapParameters("windows winrm")
+                .knifePortUseKnifeDefault();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefSoloDriver.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefSoloDriver.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefSoloDriver.java
new file mode 100644
index 0000000..0f4dd40
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefSoloDriver.java
@@ -0,0 +1,85 @@
+/*
+ * 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.chef;
+
+import org.apache.brooklyn.api.internal.EntityLocal;
+import org.apache.brooklyn.api.mgmt.TaskAdaptable;
+import org.apache.brooklyn.api.mgmt.TaskFactory;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import 
org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.util.core.task.DynamicTasks;
+
+import com.google.common.annotations.Beta;
+import com.google.common.reflect.TypeToken;
+
+/** Driver class to facilitate use of Chef */
+@Beta
+@Deprecated /** @deprecated since 0.7.0 use ChefEntity or 
ChefLifecycleEffectorTasks */
+public class ChefSoloDriver extends AbstractSoftwareProcessSshDriver 
implements ChefConfig {
+
+    @SuppressWarnings("serial")
+    public static final ConfigKey<TaskFactory<? extends 
TaskAdaptable<Boolean>>> IS_RUNNING_TASK = ConfigKeys.newConfigKey(
+            new TypeToken<TaskFactory<? extends TaskAdaptable<Boolean>>>() {}, 
+            "brooklyn.chef.task.driver.isRunningTask");
+    
+    @SuppressWarnings("serial")
+    public static final ConfigKey<TaskFactory<?>> STOP_TASK = 
ConfigKeys.newConfigKey(
+            new TypeToken<TaskFactory<?>>() {}, 
+            "brooklyn.chef.task.driver.stopTask");
+    
+    public ChefSoloDriver(EntityLocal entity, SshMachineLocation location) {
+        super(entity, location);
+    }
+
+    @Override
+    public void install() {
+        // TODO flag to force reinstallation
+        DynamicTasks.queue(
+                ChefSoloTasks.installChef(getInstallDir(), false), 
+                ChefSoloTasks.installCookbooks(getInstallDir(), 
getRequiredConfig(CHEF_COOKBOOKS), false));
+    }
+
+    @Override
+    public void customize() {
+        DynamicTasks.queue(ChefSoloTasks.buildChefFile(getRunDir(), 
getInstallDir(), "launch", getRequiredConfig(CHEF_RUN_LIST),
+                getEntity().getConfig(CHEF_LAUNCH_ATTRIBUTES)));
+    }
+
+    @Override
+    public void launch() {
+        DynamicTasks.queue(ChefSoloTasks.runChef(getRunDir(), "launch", 
getEntity().getConfig(CHEF_RUN_CONVERGE_TWICE)));
+    }
+
+    @Override
+    public boolean isRunning() {
+        return 
DynamicTasks.queue(getRequiredConfig(IS_RUNNING_TASK)).asTask().getUnchecked();
+    }
+
+    @Override
+    public void stop() {
+        DynamicTasks.queue(getRequiredConfig(STOP_TASK));
+    }
+
+    protected <T> T getRequiredConfig(ConfigKey<T> key) {
+        return ChefConfigs.getRequiredConfig(getEntity(), key);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefSoloTasks.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefSoloTasks.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefSoloTasks.java
new file mode 100644
index 0000000..64a27a4
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefSoloTasks.java
@@ -0,0 +1,70 @@
+/*
+ * 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.chef;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.mgmt.TaskFactory;
+import org.apache.brooklyn.sensor.ssh.SshEffectorTasks;
+import org.apache.brooklyn.util.ssh.BashCommands;
+
+import com.google.common.annotations.Beta;
+
+@Beta
+public class ChefSoloTasks {
+
+    public static TaskFactory<?> installChef(String chefDirectory, boolean 
force) {
+        // TODO check on entity whether it is chef _server_
+        String installCmd = cdAndRun(chefDirectory, 
ChefBashCommands.INSTALL_FROM_OPSCODE);
+        if (!force) installCmd = BashCommands.alternatives("which chef-solo", 
installCmd);
+        return SshEffectorTasks.ssh(installCmd).summary("install chef");
+    }
+
+    public static TaskFactory<?> installCookbooks(final String chefDirectory, 
final Map<String,String> cookbooksAndUrls, final boolean force) {
+        return ChefTasks.installCookbooks(chefDirectory, cookbooksAndUrls, 
force);
+    }
+
+    public static TaskFactory<?> installCookbook(String chefDirectory, String 
cookbookName, String cookbookArchiveUrl, boolean force) {
+        return ChefTasks.installCookbook(chefDirectory, cookbookName, 
cookbookArchiveUrl, force);
+    }
+
+    protected static String cdAndRun(String targetDirectory, String command) {
+        return BashCommands.chain("mkdir -p "+targetDirectory,
+                "cd "+targetDirectory,
+                command);
+    }
+
+    public static TaskFactory<?> buildChefFile(String runDirectory, String 
chefDirectory, String phase, Iterable<? extends String> runList,
+            Map<String, Object> optionalAttributes) {
+        return ChefTasks.buildChefFile(runDirectory, chefDirectory, phase, 
runList, optionalAttributes);
+    }
+
+    public static TaskFactory<?> runChef(String runDir, String phase) {
+        return runChef(runDir, phase, false);
+    }
+    /** see {@link ChefConfig#CHEF_RUN_CONVERGE_TWICE} for background on why 
'twice' is available */
+    public static TaskFactory<?> runChef(String runDir, String phase, Boolean 
twice) {
+        String cmd = "sudo chef-solo -c "+phase+".rb -j "+phase+".json 
-ldebug";
+        if (twice!=null && twice) cmd = BashCommands.alternatives(cmd, cmd);
+
+        return SshEffectorTasks.ssh(cdAndRun(runDir, cmd)).
+                summary("run chef for "+phase).requiringExitCodeZero();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefTasks.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefTasks.java 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefTasks.java
new file mode 100644
index 0000000..0f359d5
--- /dev/null
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefTasks.java
@@ -0,0 +1,153 @@
+/*
+ * 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.chef;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.TaskAdaptable;
+import org.apache.brooklyn.api.mgmt.TaskFactory;
+import org.apache.brooklyn.effector.core.EffectorTasks;
+import org.apache.brooklyn.sensor.ssh.SshEffectorTasks;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.file.ArchiveTasks;
+import org.apache.brooklyn.util.core.file.ArchiveUtils.ArchiveType;
+import org.apache.brooklyn.util.core.task.DynamicTasks;
+import org.apache.brooklyn.util.core.task.TaskBuilder;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.net.Urls;
+import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.text.Identifiers;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableList;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+@Beta
+public class ChefTasks {
+
+    private static final Logger log = LoggerFactory.getLogger(ChefTasks.class);
+    
+    public static TaskFactory<?> installChef(String chefDirectory, boolean 
force) {
+        // TODO check on entity whether it is chef _server_
+        String installCmd = cdAndRun(chefDirectory, 
ChefBashCommands.INSTALL_FROM_OPSCODE);
+        if (!force) installCmd = BashCommands.alternatives("which chef-solo", 
installCmd);
+        return SshEffectorTasks.ssh(installCmd).summary("install chef");
+    }
+
+    public static TaskFactory<?> installCookbooks(final String chefDirectory, 
final Map<String,String> cookbooksAndUrls, final boolean force) {
+        return Tasks.<Void>builder().name("install "+(cookbooksAndUrls==null ? 
"0" : cookbooksAndUrls.size())+" cookbook"+Strings.s(cookbooksAndUrls)).body(
+                new Runnable() {
+                    public void run() {
+                        Entity e = EffectorTasks.findEntity();
+                        if (cookbooksAndUrls==null)
+                            throw new IllegalStateException("No cookbooks 
defined to install at "+e);
+                        for (String cookbook: cookbooksAndUrls.keySet())
+                            DynamicTasks.queue(installCookbook(chefDirectory, 
cookbook, cookbooksAndUrls.get(cookbook), force));
+                    }
+                }).buildFactory();
+    }
+
+    public static TaskFactory<?> installCookbook(final String chefDirectory, 
final String cookbookName, final String cookbookArchiveUrl, final boolean 
force) {
+        return new TaskFactory<TaskAdaptable<?>>() {
+            @Override
+            public TaskAdaptable<?> newTask() {
+                TaskBuilder<Void> tb = Tasks.<Void>builder().name("install 
cookbook "+cookbookName);
+                
+                String cookbookDir = Urls.mergePaths(chefDirectory, 
cookbookName);
+                String privateTmpDirContainingUnpackedCookbook = 
+                    Urls.mergePaths(chefDirectory, 
"tmp-"+Strings.makeValidFilename(cookbookName)+"-"+Identifiers.makeRandomId(4));
+
+                // TODO - skip the install earlier if it exists and isn't 
forced
+//                if (!force) {
+//                    // in builder.body, check 
+//                    // "ls "+cookbookDir
+//                    // and stop if it's zero
+//                    // remove reference to 'force' below
+//                }
+                
+                String destName = null;
+                if (ArchiveType.of(cookbookArchiveUrl)==ArchiveType.UNKNOWN) {
+                    destName = cookbookName + ".tgz";
+                    log.debug("Assuming TGZ type for chef cookbook url 
"+cookbookArchiveUrl+"; it will be downloaded as "+destName);
+                }
+                tb.add(ArchiveTasks.deploy(null, null, cookbookArchiveUrl, 
EffectorTasks.findSshMachine(), privateTmpDirContainingUnpackedCookbook,
+                    false, null, destName).newTask());
+                
+                String installCmd = BashCommands.chain(
+                    "cd "+privateTmpDirContainingUnpackedCookbook,  
+                    "COOKBOOK_EXPANDED_DIR=`ls`",
+                    BashCommands.requireTest("`ls | wc -w` -eq 1", 
+                            "The deployed archive "+cookbookArchiveUrl+" must 
contain exactly one directory"),
+                    "mv $COOKBOOK_EXPANDED_DIR '../"+cookbookName+"'",
+                    "cd ..",
+                    "rm -rf '"+privateTmpDirContainingUnpackedCookbook+"'");
+                
+                installCmd = force ? BashCommands.alternatives("rm -rf 
"+cookbookDir, installCmd) : BashCommands.alternatives("ls "+cookbookDir+" > 
/dev/null 2> /dev/null", installCmd);
+                tb.add(SshEffectorTasks.ssh(installCmd).summary("renaming 
cookbook dir").requiringExitCodeZero().newTask());
+                
+                return tb.build();
+            }
+        };
+    }
+
+    protected static String cdAndRun(String targetDirectory, String command) {
+        return BashCommands.chain("mkdir -p '"+targetDirectory+"'",
+                "cd '"+targetDirectory+"'",
+                command);
+    }
+
+    public static TaskFactory<?> buildChefFile(String runDirectory, String 
chefDirectory, String phase, Iterable<? extends String> runList,
+            Map<String, Object> optionalAttributes) {
+        // TODO if it's server, try knife first
+        // TODO configure add'l properties
+        String phaseRb = 
+            "root = "
+            + "'"+runDirectory+"'" 
+            // recommended alternate to runDir is the following, but it is not 
available in some rubies
+            //+ File.absolute_path(File.dirname(__FILE__))"+
+            + "\n"+
+            "file_cache_path root\n"+
+//            "cookbook_path root + '/cookbooks'\n";
+            "cookbook_path '"+chefDirectory+"'\n";
+
+        Map<String,Object> phaseJsonMap = MutableMap.of();
+        if (optionalAttributes!=null)
+            phaseJsonMap.putAll(optionalAttributes);
+        if (runList!=null)
+            phaseJsonMap.put("run_list", ImmutableList.copyOf(runList));
+        Gson json = new GsonBuilder().create();
+        String phaseJson = json.toJson(phaseJsonMap);
+
+        return Tasks.sequential("build chef files for "+phase,
+                    
SshEffectorTasks.put(Urls.mergePaths(runDirectory)+"/"+phase+".rb").contents(phaseRb).createDirectory(),
+                    
SshEffectorTasks.put(Urls.mergePaths(runDirectory)+"/"+phase+".json").contents(phaseJson));
+    }
+
+    public static TaskFactory<?> runChef(String runDir, String phase) {
+        // TODO chef server
+        return SshEffectorTasks.ssh(cdAndRun(runDir, "sudo chef-solo -c 
"+phase+".rb -j "+phase+".json -ldebug")).
+                summary("run chef for "+phase).requiringExitCodeZero();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/KnifeConvergeTaskFactory.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/chef/KnifeConvergeTaskFactory.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/KnifeConvergeTaskFactory.java
new file mode 100644
index 0000000..39e31b8
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/KnifeConvergeTaskFactory.java
@@ -0,0 +1,246 @@
+/*
+ * 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.chef;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static 
org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes.wrapBash;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.base.Strings;
+import com.google.common.net.HostAndPort;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.effector.core.EffectorTasks;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.util.collections.Jsonya;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
+import org.apache.brooklyn.util.ssh.BashCommands;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.gson.GsonBuilder;
+
+public class KnifeConvergeTaskFactory<RET> extends KnifeTaskFactory<RET> {
+
+    private static final Logger log = 
LoggerFactory.getLogger(KnifeConvergeTaskFactory.class);
+    
+    protected Function<? super Entity,String> runList;
+    protected Map<Object, Object> knifeAttributes = new MutableMap<Object, 
Object>();
+    protected List<String> extraBootstrapParameters = MutableList.of();
+    protected Boolean sudo;
+    protected Boolean runTwice;
+    protected String nodeName;
+    protected Integer port;
+    /** null means nothing specified, use user supplied or machine default;
+     * false means use machine default (disallow user supplied);
+     * true means use knife default (omit the argument and disallow user 
supplied)
+     */
+    protected Boolean portOmittedToUseKnifeDefault;
+
+    public KnifeConvergeTaskFactory(String taskName) {
+        super(taskName);
+    }
+    
+    @Override
+    protected KnifeConvergeTaskFactory<RET> self() {
+        return this;
+    }
+    
+    /** construct the knife command, based on the settings on other methods
+     * (called when instantiating the script, after all parameters sent)
+     */
+    protected List<String> initialKnifeParameters() {
+        // runs inside the task so can detect entity/machine at runtime
+        MutableList<String> result = new MutableList<String>();
+        SshMachineLocation machine = EffectorTasks.findSshMachine();
+        
+        result.add("bootstrap");
+        result.addAll(extraBootstrapParameters);
+
+        HostAndPort hostAndPort = machine.getSshHostAndPort();
+        result.add(wrapBash(hostAndPort.getHostText()));
+        Integer whichPort = knifeWhichPort(hostAndPort);
+        if (whichPort!=null)
+            result.add("-p "+whichPort);
+
+        result.add("-x "+wrapBash(checkNotNull(machine.getUser(), "user")));
+        
+        File keyfile = ChefServerTasks.extractKeyFile(machine);
+        if (keyfile!=null) result.add("-i "+keyfile.getPath());
+        else result.add("-P "+checkNotNull(machine.findPassword(), "No 
password or private key data for "+machine));
+        
+        result.add("--no-host-key-verify");
+        
+        if (sudo != Boolean.FALSE) result.add("--sudo");
+
+        if (!Strings.isNullOrEmpty(nodeName)) {
+            result.add("--node-name");
+            result.add(nodeName);
+        }
+
+        result.add("-r "+wrapBash(runList.apply(entity())));
+        
+        if (!knifeAttributes.isEmpty())
+            result.add("-j "+wrapBash(new GsonBuilder().create()
+                    .toJson(knifeAttributes)));
+
+        return result;
+    }
+    
+    /** whether knife should attempt to run twice;
+     * see {@link ChefConfig#CHEF_RUN_CONVERGE_TWICE} */
+    public KnifeConvergeTaskFactory<RET> knifeRunTwice(boolean runTwice) {
+        this.runTwice = runTwice;
+        return self();
+    }
+    
+    /** whether to pass --sudo to knife; default true */
+    public KnifeConvergeTaskFactory<RET> knifeSudo(boolean sudo) {
+        this.sudo = sudo;
+        return self();
+    }
+
+    /** what node name to pass to knife; default = null, meaning chef-client 
will pick the node name */
+    public KnifeConvergeTaskFactory<RET> knifeNodeName(String nodeName) {
+        this.nodeName = nodeName;
+        return self();
+    }
+
+    /** tell knife to use an explicit port */
+    public KnifeConvergeTaskFactory<RET> knifePort(int port) {
+        if (portOmittedToUseKnifeDefault!=null) {
+            log.warn("Port "+port+" specified to "+this+" for when already 
explicitly told to use a default (overriding previous); see subsequent warning 
for more details");
+        }
+        this.port = port;
+        return self();
+    }
+
+    /** omit the port parameter altogether (let knife use its default) */
+    public KnifeConvergeTaskFactory<RET> knifePortUseKnifeDefault() {
+        if (port!=null) {
+            log.warn("knifePortUseKnifeDefault specified to "+this+" when 
already told to use "+port+" explicitly (overriding previous); see subsequent 
warning for more details");
+            port = -1;
+        }
+        portOmittedToUseKnifeDefault = true;
+        return self();
+    }
+    
+    /** use the default port known to brooklyn for the target machine for ssh 
*/
+    public KnifeConvergeTaskFactory<RET> knifePortUseMachineSshPort() {
+        if (port!=null) {
+            log.warn("knifePortUseMachineSshPort specified to "+this+" when 
already told to use "+port+" explicitly (overriding previous); see subsequent 
warning for more details");
+            port = -1;
+        }
+        portOmittedToUseKnifeDefault = false;
+        return self();
+    }
+    
+    protected Integer knifeWhichPort(HostAndPort hostAndPort) {
+        if (port==null) {
+            if (Boolean.TRUE.equals(portOmittedToUseKnifeDefault))
+                // user has explicitly said to use knife default, omitting 
port here
+                return null;
+            // default is to use the machine port
+            return hostAndPort.getPort();
+        }
+        if (port==-1) {
+            // port was supplied by user, then portDefault (true or false)
+            port = null;
+            Integer whichPort = knifeWhichPort(hostAndPort);
+            log.warn("knife port conflicting instructions for "+this+" at 
entity "+entity()+" on "+hostAndPort+"; using default ("+whichPort+")");
+            return whichPort;
+        }
+        if (portOmittedToUseKnifeDefault!=null) {
+            // portDefault was specified (true or false), then overridden with 
a port
+            log.warn("knife port conflicting instructions for "+this+" at 
entity "+entity()+" on "+hostAndPort+"; using supplied port "+port);
+        }
+        // port was supplied by user, use that
+        return port;
+    }
+    
+    /** parameters to pass to knife after the bootstrap command */
+    public KnifeConvergeTaskFactory<RET> 
knifeAddExtraBootstrapParameters(String extraBootstrapParameter1, String 
...extraBootstrapParameters) {
+        this.extraBootstrapParameters.add(extraBootstrapParameter1);
+        for (String p: extraBootstrapParameters)
+            this.extraBootstrapParameters.add(p);
+        return self();
+    }
+    
+    /** function supplying the run list to be passed to knife, evaluated at 
the last moment */
+    public KnifeConvergeTaskFactory<RET> knifeRunList(Function<? super Entity, 
String> runList) {
+        this.runList = runList;
+        return self();
+    }
+    public KnifeConvergeTaskFactory<RET> knifeRunList(String runList) {
+        this.runList = Functions.constant(runList);
+        return self();
+    }
+    
+    /** includes the given attributes in the attributes to be passed to chef; 
+     * when combining with other attributes, this uses {@link Jsonya} 
semantics to add 
+     * (a deep add, combining lists and maps) */
+    public KnifeConvergeTaskFactory<RET> knifeAddAttributes(Map<? extends 
Object, ? extends Object> attributes) {
+        if (attributes!=null && !attributes.isEmpty()) {
+            Jsonya.of(knifeAttributes).add(attributes);
+        }
+        return self();
+    }
+    
+    protected String buildKnifeCommand(int knifeCommandIndex) {
+        String result = super.buildKnifeCommand(knifeCommandIndex);
+        if (Boolean.TRUE.equals(runTwice))
+            result = BashCommands.alternatives(result, result);
+        return result;
+    }
+
+    @Override
+    public <T2> KnifeConvergeTaskFactory<T2> returning(ScriptReturnType type) {
+        return (KnifeConvergeTaskFactory<T2>) super.<T2>returning(type);
+    }
+
+    @Override
+    public <RET2> KnifeConvergeTaskFactory<RET2> 
returning(Function<ProcessTaskWrapper<?>, RET2> resultTransformation) {
+        return (KnifeConvergeTaskFactory<RET2>) 
super.returning(resultTransformation);
+    }
+    
+    @Override
+    public KnifeConvergeTaskFactory<Boolean> returningIsExitCodeZero() {
+        return (KnifeConvergeTaskFactory<Boolean>) 
super.returningIsExitCodeZero();
+    }
+    
+    @Override
+    public KnifeConvergeTaskFactory<String> requiringZeroAndReturningStdout() {
+        return (KnifeConvergeTaskFactory<String>) 
super.requiringZeroAndReturningStdout();
+    }
+
+    public KnifeConvergeTaskFactory<RET> knifeAddParameters(String word1, 
String ...words) {
+        super.knifeAddParameters(word1, words);
+        return self();
+    }
+
+    // TODO other methods from KnifeTaskFactory will return KTF class not KCTF;
+    // should make it generic so it returns the right type...
+}
\ 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/chef/KnifeTaskFactory.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/chef/KnifeTaskFactory.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/KnifeTaskFactory.java
new file mode 100644
index 0000000..5d1af9d
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/chef/KnifeTaskFactory.java
@@ -0,0 +1,240 @@
+/*
+ * 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.chef;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.core.internal.ssh.process.ProcessTool;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.core.task.system.ProcessTaskFactory;
+import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
+import 
org.apache.brooklyn.util.core.task.system.internal.SystemProcessTaskFactory;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes;
+
+import com.google.common.base.Function;
+
+/** A factory which acts like {@link ProcessTaskFactory} with special options 
for knife.
+ * Typical usage is to {@link #addKnifeParameters(String)}s for the knife 
command to be run.
+ * You can also {@link #add(String...)} commands as needed; these will run 
*before* knife,
+ * unless you addKnifeCommandHere().
+ * <p>
+ * This impl will use sensible defaults, including {@link ConfigKey}s on the 
context entity,
+ * for general knife config but not specific commands etc. It supports:
+ * <li> {@link ChefConfig#KNIFE_EXECUTABLE}
+ * <li> {@link ChefConfig#KNIFE_CONFIG_FILE}
+ * <p>
+ * (Other fields will typically be used by methods calling to this factory.) 
+ *  */
+// see e.g. http://docs.opscode.com/knife_bootstrap.html
+public class KnifeTaskFactory<RET> extends 
SystemProcessTaskFactory<KnifeTaskFactory<RET>, RET>{
+    
+    private static String KNIFE_PLACEHOLDER = "<knife command goes here 1234>";
+    public final String taskName;
+    protected String knifeExecutable;
+    protected List<String> knifeParameters = new ArrayList<String>();
+    protected String knifeConfigFile;
+    protected String knifeSetupCommands;
+    protected Boolean throwOnCommonKnifeErrors;
+    
+    public KnifeTaskFactory(String taskName) {
+        this.taskName = taskName;
+        summary(taskName);
+        // knife setup usually requires a login shell
+        config.put(ProcessTool.PROP_LOGIN_SHELL, true);
+    }
+    
+    @Override
+    public List<Function<ProcessTaskWrapper<?>, Void>> 
getCompletionListeners() {
+        MutableList<Function<ProcessTaskWrapper<?>, Void>> result = 
MutableList.copyOf(super.getCompletionListeners());
+        if (throwOnCommonKnifeErrors != Boolean.FALSE)
+            insertKnifeCompletionListenerIntoCompletionListenersList(result);
+        return result.asUnmodifiable();
+    }
+    
+    public KnifeTaskFactory<RET> notThrowingOnCommonKnifeErrors() {
+        throwOnCommonKnifeErrors = false;
+        return self();
+    }
+
+    protected void 
insertKnifeCompletionListenerIntoCompletionListenersList(List<Function<ProcessTaskWrapper<?>,
 Void>> listeners) {
+        // give a nice warning if chef/knife not set up correctly
+        Function<ProcessTaskWrapper<?>, Void> 
propagateIfKnifeConfigFileMissing = new Function<ProcessTaskWrapper<?>, Void>() 
{
+            public Void apply(@Nullable ProcessTaskWrapper<?> input) {
+                if (input.getExitCode()!=0 && 
input.getStderr().indexOf("WARNING: No knife configuration file found")>=0) {
+                    String myConfig = knifeConfigFileOption();
+                    if (Strings.isEmpty(myConfig))
+                        throw new IllegalStateException("Config file for Chef 
knife must be specified in "+ChefConfig.KNIFE_CONFIG_FILE+" (or valid knife 
default set up)");
+                    else
+                        throw new IllegalStateException("Error reading config 
file for Chef knife ("+myConfig+") -- does it exist?");
+                }
+                return null;
+            }
+        };
+        listeners.add(propagateIfKnifeConfigFileMissing);
+    }
+
+    
+    @Override
+    public ProcessTaskWrapper<RET> newTask() {
+        return new SystemProcessTaskWrapper("Knife");
+    }
+
+    /** Inserts the knife command at the current place in the list.
+     * Can be run multiple times. The knife command added at the end of the 
list
+     * if this is not invoked (and it is the only command if nothing is {@link 
#add(String...)}ed.
+     */
+    public KnifeTaskFactory<RET> addKnifeCommandToScript() {
+        add(KNIFE_PLACEHOLDER);
+        return self();
+    }
+    
+    @Override
+    public List<String> getCommands() {
+        MutableList<String> result = new MutableList<String>();
+        String setupCommands = knifeSetupCommands();
+        if (setupCommands != null && Strings.isNonBlank(setupCommands))
+            result.add(setupCommands);
+        int numKnifes = 0;
+        for (String c: super.getCommands()) {
+            if (c==KNIFE_PLACEHOLDER)
+                result.add(buildKnifeCommand(numKnifes++));
+            else
+                result.add(c);
+        }
+        if (numKnifes==0)
+            result.add(buildKnifeCommand(numKnifes++));
+        return result.asUnmodifiable();
+    }
+    
+    /** creates the command for running knife.
+     * in some cases knife may be added multiple times,
+     * and in that case the parameter here tells which time it is being added, 
+     * on a single run. */
+    protected String buildKnifeCommand(int knifeCommandIndex) {
+        MutableList<String> words = new MutableList<String>();
+        words.add(knifeExecutable());
+        words.addAll(initialKnifeParameters());
+        words.addAll(knifeParameters());
+        String x = knifeConfigFileOption();
+        if (Strings.isNonBlank(x)) words.add(knifeConfigFileOption());
+        return Strings.join(words, " ");
+    }
+    
+    /** allows a way for subclasses to build up parameters at the start */
+    protected List<String> initialKnifeParameters() {
+        return new MutableList<String>();
+    }
+    
+    @Nullable /** callers should allow this to be null so task can be used 
outside of an entity */
+    protected Entity entity() {
+        return BrooklynTaskTags.getTargetOrContextEntity(Tasks.current());
+    }
+    protected <T> T entityConfig(ConfigKey<T> key) {
+        Entity entity = entity();
+        if (entity!=null)
+            return entity.getConfig(key);
+        return null;
+    }
+    
+    public KnifeTaskFactory<RET> knifeExecutable(String knifeExecutable) {
+        this.knifeExecutable = knifeExecutable;
+        return this;
+    }
+    
+    protected String knifeExecutable() {
+        if (knifeExecutable!=null) return knifeExecutable;
+        
+        String knifeExecFromConfig = entityConfig(ChefConfig.KNIFE_EXECUTABLE);
+        if (knifeExecFromConfig!=null) return 
BashStringEscapes.wrapBash(knifeExecFromConfig);
+        
+        // assume on the path, if executable not set
+        return "knife";
+    }
+    
+    protected List<String> knifeParameters() {
+        return knifeParameters;
+    }
+    
+    public KnifeTaskFactory<RET> knifeAddParameters(String word1, String 
...words) {
+        knifeParameters.add(word1);
+        for (String w: words)
+            knifeParameters.add(w);
+        return self();
+    }
+
+    public KnifeTaskFactory<RET> knifeConfigFile(String knifeConfigFile) {
+        this.knifeConfigFile = knifeConfigFile;
+        return self();
+    }
+    
+    @Nullable
+    protected String knifeConfigFileOption() {
+        if (knifeConfigFile!=null) return "-c "+knifeConfigFile;
+
+        String knifeConfigFileFromConfig = 
entityConfig(ChefConfig.KNIFE_CONFIG_FILE);
+        if (knifeConfigFileFromConfig!=null) return "-c 
"+BashStringEscapes.wrapBash(knifeConfigFileFromConfig);
+
+        // if not supplied will use global config
+        return null;
+    }
+
+    public KnifeTaskFactory<RET> knifeSetupCommands(String knifeSetupCommands) 
{
+        this.knifeSetupCommands = knifeSetupCommands;
+        return self();
+    }
+    
+    @Nullable
+    protected String knifeSetupCommands() {
+        if (knifeSetupCommands!=null) return knifeSetupCommands;
+        
+        String knifeSetupCommandsFromConfig = 
entityConfig(ChefConfig.KNIFE_SETUP_COMMANDS);
+        if (knifeSetupCommandsFromConfig!=null) return 
knifeSetupCommandsFromConfig;
+        
+        // if not supplied will use global config
+        return null;
+    }
+    
+    @Override
+    public <T2> KnifeTaskFactory<T2> returning(ScriptReturnType type) {
+        return (KnifeTaskFactory<T2>) super.<T2>returning(type);
+    }
+
+    @Override
+    public <RET2> KnifeTaskFactory<RET2> 
returning(Function<ProcessTaskWrapper<?>, RET2> resultTransformation) {
+        return (KnifeTaskFactory<RET2>) super.returning(resultTransformation);
+    }
+    
+    @Override
+    public KnifeTaskFactory<Boolean> returningIsExitCodeZero() {
+        return (KnifeTaskFactory<Boolean>) super.returningIsExitCodeZero();
+    }
+    
+    @Override
+    public KnifeTaskFactory<String> requiringZeroAndReturningStdout() {
+        return (KnifeTaskFactory<String>) 
super.requiringZeroAndReturningStdout();
+    }
+}
\ 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/JavaAppUtils.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaAppUtils.java 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaAppUtils.java
new file mode 100644
index 0000000..81811fb
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaAppUtils.java
@@ -0,0 +1,263 @@
+/*
+ * 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.lang.management.ManagementFactory;
+import java.lang.management.MemoryUsage;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.management.openmbean.CompositeData;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.internal.EntityLocal;
+import org.apache.brooklyn.core.config.render.RendererHints;
+import org.apache.brooklyn.policy.enricher.RollingTimeWindowMeanEnricher;
+import org.apache.brooklyn.policy.enricher.TimeFractionDeltaEnricher;
+import org.apache.brooklyn.sensor.feed.http.HttpValueFunctions;
+import org.apache.brooklyn.sensor.feed.jmx.JmxAttributePollConfig;
+import org.apache.brooklyn.sensor.feed.jmx.JmxFeed;
+import org.apache.brooklyn.util.math.MathFunctions;
+import org.apache.brooklyn.util.text.ByteSizeStrings;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.time.Time;
+
+import com.google.common.base.Function;
+
+public class JavaAppUtils {
+
+    public static boolean isEntityMxBeanStatsEnabled(Entity entity) {
+        return 
groovyTruth(entity.getConfig(UsesJavaMXBeans.MXBEAN_STATS_ENABLED));
+    }
+
+    /**
+     * @see #connectJavaAppServerPolicies(EntityLocal, Duration)
+     * @see #getMxBeanSensorsBuilder(EntityLocal)
+     */
+    @Nullable
+    public static JmxFeed connectMXBeanSensors(EntityLocal entity) {
+        if (isEntityMxBeanStatsEnabled(entity)) {
+            return getMxBeanSensorsBuilder(entity).build();
+        } else {
+            return null;
+        }
+    }
+
+    /** @see #connectJavaAppServerPolicies(EntityLocal, Duration) */
+    @Nullable
+    public static JmxFeed connectMXBeanSensors(EntityLocal entity, long 
jmxPollPeriodMs) {
+        if (isEntityMxBeanStatsEnabled(entity)) {
+            return getMxBeanSensorsBuilder(entity, jmxPollPeriodMs).build();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * @param entity The entity at which to poll
+     * @param jmxPollPeriod How often to poll
+     * @return A {@link JmxFeed} configured to poll the given entity at the 
given period,
+     *         or null if the entity is not configured for MXBEAN_STATS
+     * @see 
org.apache.brooklyn.entity.java.UsesJavaMXBeans#MXBEAN_STATS_ENABLED
+     */
+    @Nullable
+    public static JmxFeed connectMXBeanSensors(EntityLocal entity, Duration 
jmxPollPeriod) {
+        if (isEntityMxBeanStatsEnabled(entity)) {
+            return getMxBeanSensorsBuilder(entity, jmxPollPeriod).build();
+        } else {
+            return null;
+        }
+    }
+    
+    public static void connectJavaAppServerPolicies(EntityLocal entity) {
+        connectJavaAppServerPolicies(entity, Duration.TEN_SECONDS);
+    }
+
+    public static void connectJavaAppServerPolicies(EntityLocal entity, 
Duration windowPeriod) {
+        entity.addEnricher(new TimeFractionDeltaEnricher<Double>(entity, 
UsesJavaMXBeans.PROCESS_CPU_TIME, 
+                UsesJavaMXBeans.PROCESS_CPU_TIME_FRACTION_LAST, 
TimeUnit.MILLISECONDS));
+
+        entity.addEnricher(new RollingTimeWindowMeanEnricher<Double>(entity,
+                UsesJavaMXBeans.PROCESS_CPU_TIME_FRACTION_LAST, 
UsesJavaMXBeans.PROCESS_CPU_TIME_FRACTION_IN_WINDOW,
+                windowPeriod));
+    }
+
+    /**
+     * @param entity The entity at which to poll
+     * @return A {@link JmxFeed.Builder} configured to poll entity every ten 
seconds
+     * @see #getMxBeanSensorsBuilder(EntityLocal, Duration)
+     */
+    @Nonnull
+    public static JmxFeed.Builder getMxBeanSensorsBuilder(EntityLocal entity) {
+        return getMxBeanSensorsBuilder(entity, Duration.TEN_SECONDS);
+    }
+
+    /** @see #getMxBeanSensorsBuilder(EntityLocal, Duration) */
+    @Nonnull
+    public static JmxFeed.Builder getMxBeanSensorsBuilder(EntityLocal entity, 
long jmxPollPeriod) {
+        return getMxBeanSensorsBuilder(entity, Duration.millis(jmxPollPeriod));
+    }
+
+    /**
+     * @param entity The entity at which to poll
+     * @param jmxPollPeriod How often to poll
+     * @return A {@link JmxFeed.Builder} configured to poll many interesting 
MXBeans
+     *         at the given entity and to repeat according to the given poll 
period.
+     *         <p/>
+     *         If an entity does not have MXBean stats enabled (i.e. {@link 
UsesJavaMXBeans#MXBEAN_STATS_ENABLED} is
+     *         configured to false) then returns a builder configured with 
entity and duration but no polls.
+     *         <p/>
+     *         Use {@link #connectMXBeanSensors(EntityLocal, Duration)} to 
create and build in one step.
+     */
+    @Nonnull
+    @SuppressWarnings({"unchecked"})
+    public static JmxFeed.Builder getMxBeanSensorsBuilder(EntityLocal entity, 
Duration jmxPollPeriod) {
+        JmxFeed.Builder builder = JmxFeed.builder()
+                .entity(entity)
+                .period(jmxPollPeriod);
+        if (isEntityMxBeanStatsEnabled(entity)) {
+            // TODO Could we reuse the result of compositeDataToMemoryUsage?
+            builder
+                .pollAttribute(new 
JmxAttributePollConfig<Long>(UsesJavaMXBeans.USED_HEAP_MEMORY)
+                        .objectName(ManagementFactory.MEMORY_MXBEAN_NAME)
+                        .attributeName("HeapMemoryUsage")
+                        .onSuccess((Function) 
HttpValueFunctions.chain(compositeDataToMemoryUsage(), new 
Function<MemoryUsage, Long>() {
+                            @Override public Long apply(MemoryUsage input) {
+                                return (input == null) ? null : 
input.getUsed();
+                            }})))
+                .pollAttribute(new 
JmxAttributePollConfig<Long>(UsesJavaMXBeans.INIT_HEAP_MEMORY)
+                        .objectName(ManagementFactory.MEMORY_MXBEAN_NAME)
+                        .attributeName("HeapMemoryUsage")
+                        .onSuccess((Function) 
HttpValueFunctions.chain(compositeDataToMemoryUsage(), new 
Function<MemoryUsage, Long>() {
+                            @Override public Long apply(MemoryUsage input) {
+                                return (input == null) ? null : 
input.getInit();
+                            }})))
+                .pollAttribute(new 
JmxAttributePollConfig<Long>(UsesJavaMXBeans.COMMITTED_HEAP_MEMORY)
+                        .objectName(ManagementFactory.MEMORY_MXBEAN_NAME)
+                        .attributeName("HeapMemoryUsage")
+                        .onSuccess((Function) 
HttpValueFunctions.chain(compositeDataToMemoryUsage(), new 
Function<MemoryUsage, Long>() {
+                            @Override public Long apply(MemoryUsage input) {
+                                return (input == null) ? null : 
input.getCommitted();
+                            }})))
+                .pollAttribute(new 
JmxAttributePollConfig<Long>(UsesJavaMXBeans.MAX_HEAP_MEMORY)
+                        .objectName(ManagementFactory.MEMORY_MXBEAN_NAME)
+                        .attributeName("HeapMemoryUsage")
+                        .onSuccess((Function) 
HttpValueFunctions.chain(compositeDataToMemoryUsage(), new 
Function<MemoryUsage, Long>() {
+                            @Override public Long apply(MemoryUsage input) {
+                                return (input == null) ? null : input.getMax();
+                            }})))
+                .pollAttribute(new 
JmxAttributePollConfig<Long>(UsesJavaMXBeans.NON_HEAP_MEMORY_USAGE)
+                        .objectName(ManagementFactory.MEMORY_MXBEAN_NAME)
+                        .attributeName("NonHeapMemoryUsage")
+                        .onSuccess((Function) 
HttpValueFunctions.chain(compositeDataToMemoryUsage(), new 
Function<MemoryUsage, Long>() {
+                            @Override public Long apply(MemoryUsage input) {
+                                return (input == null) ? null : 
input.getUsed();
+                            }})))
+
+                .pollAttribute(new 
JmxAttributePollConfig<Integer>(UsesJavaMXBeans.CURRENT_THREAD_COUNT)
+                        .objectName(ManagementFactory.THREAD_MXBEAN_NAME)
+                        .attributeName("ThreadCount"))
+                .pollAttribute(new 
JmxAttributePollConfig<Integer>(UsesJavaMXBeans.PEAK_THREAD_COUNT)
+                        .objectName(ManagementFactory.THREAD_MXBEAN_NAME)
+                        .attributeName("PeakThreadCount"))
+
+                .pollAttribute(new 
JmxAttributePollConfig<Long>(UsesJavaMXBeans.START_TIME)
+                        .objectName(ManagementFactory.RUNTIME_MXBEAN_NAME)
+                        .period(60, TimeUnit.SECONDS)
+                        .attributeName("StartTime"))
+                .pollAttribute(new 
JmxAttributePollConfig<Long>(UsesJavaMXBeans.UP_TIME)
+                        .objectName(ManagementFactory.RUNTIME_MXBEAN_NAME)
+                        .period(60, TimeUnit.SECONDS)
+                        .attributeName("Uptime"))
+
+                .pollAttribute(new 
JmxAttributePollConfig<Double>(UsesJavaMXBeans.PROCESS_CPU_TIME)
+                        
.objectName(ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME)
+                        .attributeName("ProcessCpuTime")
+                        .onSuccess((Function) 
MathFunctions.times(0.001*0.001)))   // nanos to millis
+                .pollAttribute(new 
JmxAttributePollConfig<Double>(UsesJavaMXBeans.SYSTEM_LOAD_AVERAGE)
+                        
.objectName(ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME)
+                        .attributeName("SystemLoadAverage"))
+                .pollAttribute(new 
JmxAttributePollConfig<Integer>(UsesJavaMXBeans.AVAILABLE_PROCESSORS)
+                        
.objectName(ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME)
+                        .period(60, TimeUnit.SECONDS)
+                        .attributeName("AvailableProcessors"))
+                .pollAttribute(new 
JmxAttributePollConfig<Long>(UsesJavaMXBeans.TOTAL_PHYSICAL_MEMORY_SIZE)
+                        
.objectName(ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME)
+                        .period(60, TimeUnit.SECONDS)
+                        .attributeName("TotalPhysicalMemorySize"))
+                .pollAttribute(new 
JmxAttributePollConfig<Long>(UsesJavaMXBeans.FREE_PHYSICAL_MEMORY_SIZE)
+                        
.objectName(ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME)
+                        .period(60, TimeUnit.SECONDS)
+                        .attributeName("FreePhysicalMemorySize"));
+         //FIXME: need a new type of adapter that maps multiple objectNames to 
a mapping
+         // 
jmxAdapter.objectName(ManagementFactory.GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE + 
",*").with {
+         //     
attribute("SystemLoadAverage").subscribe(UsesJavaMXBeans.GARBAGE_COLLECTION_TIME,
 { def m -> log.info("XXXXXXX $m") });
+         // }
+        }
+        return builder;
+    }
+
+    /** @deprecated Since 0.7.0. Use {@link 
org.apache.brooklyn.util.math.MathFunctions#times(double)} instead */
+    @Deprecated
+    public static Function<Number, Double> times(final double x) {
+        return MathFunctions.times(x);
+    }
+
+    public static Function<CompositeData, MemoryUsage> 
compositeDataToMemoryUsage() {
+        return new Function<CompositeData, MemoryUsage>() {
+            @Override public MemoryUsage apply(CompositeData input) {
+                return (input == null) ? null : MemoryUsage.from(input);
+            }
+        };
+    }
+
+    private static final AtomicBoolean initialized = new AtomicBoolean(false);
+
+    /** Setup renderer hints for the MXBean attributes. */
+    public static void init() {
+        if (initialized.get()) return;
+        synchronized (initialized) {
+            if (initialized.get()) return;
+
+            RendererHints.register(UsesJavaMXBeans.USED_HEAP_MEMORY, 
RendererHints.displayValue(ByteSizeStrings.metric()));
+            RendererHints.register(UsesJavaMXBeans.INIT_HEAP_MEMORY, 
RendererHints.displayValue(ByteSizeStrings.metric()));
+            RendererHints.register(UsesJavaMXBeans.MAX_HEAP_MEMORY, 
RendererHints.displayValue(ByteSizeStrings.metric()));
+            RendererHints.register(UsesJavaMXBeans.COMMITTED_HEAP_MEMORY, 
RendererHints.displayValue(ByteSizeStrings.metric()));
+            RendererHints.register(UsesJavaMXBeans.NON_HEAP_MEMORY_USAGE, 
RendererHints.displayValue(ByteSizeStrings.metric()));
+            RendererHints.register(UsesJavaMXBeans.TOTAL_PHYSICAL_MEMORY_SIZE, 
RendererHints.displayValue(ByteSizeStrings.metric()));
+            RendererHints.register(UsesJavaMXBeans.FREE_PHYSICAL_MEMORY_SIZE, 
RendererHints.displayValue(ByteSizeStrings.metric()));
+
+            RendererHints.register(UsesJavaMXBeans.START_TIME, 
RendererHints.displayValue(Time.toDateString()));
+            RendererHints.register(UsesJavaMXBeans.UP_TIME, 
RendererHints.displayValue(Duration.millisToStringRounded()));
+            RendererHints.register(UsesJavaMXBeans.PROCESS_CPU_TIME, 
RendererHints.displayValue(Duration.millisToStringRounded()));
+            
RendererHints.register(UsesJavaMXBeans.PROCESS_CPU_TIME_FRACTION_LAST, 
RendererHints.displayValue(MathFunctions.percent(4)));
+            
RendererHints.register(UsesJavaMXBeans.PROCESS_CPU_TIME_FRACTION_IN_WINDOW, 
RendererHints.displayValue(MathFunctions.percent(4)));
+
+            initialized.set(true);
+        }
+    }
+
+    static {
+        init();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaEntityMethods.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaEntityMethods.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaEntityMethods.java
new file mode 100644
index 0000000..44afc8d
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaEntityMethods.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/** DSL conveniences for Java entities. Also see {@link JavaAppUtils} for 
methods useful within Java classes. */
+public class JavaEntityMethods {
+
+    public static ConfigKey<String> javaSysProp(String propertyName) { return 
UsesJava.JAVA_SYSPROPS.subKey(propertyName); }
+    
+    // TODO javaMaxHeap javaInitialHeap javaMaxPermGen should all be supplied 
as ListConfigs on JAVA_OPTIONS
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessDriver.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessDriver.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessDriver.java
new file mode 100644
index 0000000..627a5d2
--- /dev/null
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessDriver.java
@@ -0,0 +1,30 @@
+/*
+ * 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.entity.software.base.SoftwareProcessDriver;
+
+/**
+ * A {@link SoftwareProcessDriver} for Java processes.
+ */
+public interface JavaSoftwareProcessDriver extends SoftwareProcessDriver {
+
+    public boolean isJmxEnabled();
+        
+}

Reply via email to