http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4f9d4ce8/usage/cli/src/main/java/org/apache/brooklyn/cli/CloudExplorer.java
----------------------------------------------------------------------
diff --git a/usage/cli/src/main/java/org/apache/brooklyn/cli/CloudExplorer.java 
b/usage/cli/src/main/java/org/apache/brooklyn/cli/CloudExplorer.java
new file mode 100644
index 0000000..044042a
--- /dev/null
+++ b/usage/cli/src/main/java/org/apache/brooklyn/cli/CloudExplorer.java
@@ -0,0 +1,381 @@
+/*
+ * 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.cli;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import io.airlift.command.Command;
+import io.airlift.command.Option;
+import io.airlift.command.ParseException;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.jclouds.blobstore.BlobStore;
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.blobstore.domain.Blob;
+import org.jclouds.blobstore.domain.StorageMetadata;
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.domain.ComputeMetadata;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.options.TemplateOptions;
+
+import brooklyn.location.Location;
+import brooklyn.location.LocationDefinition;
+import brooklyn.location.basic.LocationConfigKeys;
+import brooklyn.location.cloud.CloudLocationConfig;
+import brooklyn.location.jclouds.JcloudsLocation;
+import brooklyn.location.jclouds.JcloudsUtil;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.util.exceptions.FatalConfigurationRuntimeException;
+import brooklyn.util.stream.Streams;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.collect.Lists;
+
+/**
+ * Convenience for listing Cloud Compute and BlobStore details.
+ * <p>
+ * For fuller functionality, consider instead the jclouds CLI or Ruby Fog CLI.
+ * <p>
+ * The advantage of this utility is that it piggie-backs off the {@code 
brooklyn.property} credentials,
+ * so requires less additional credential configuration. It also gives 
brooklyn-specific information,
+ * such as which image will be used by default in a given cloud.
+ */
+public class CloudExplorer {
+
+    public static abstract class JcloudsCommand extends 
AbstractMain.BrooklynCommandCollectingArgs {
+        @Option(name = { "--all-locations" }, title = "all locations",
+                description = "All locations (i.e. all locations in 
brooklyn.properties for which there are credentials)")
+        public boolean allLocations;
+        
+        @Option(name = { "-l", "--location" }, title = "location spec",
+                description = "A location spec (e.g. referring to a named 
location in brooklyn.properties file)")
+        public String location;
+
+        @Option(name = { "-y", "--yes" }, title = "auto-confirm",
+                description = "Automatically answer yes to any questions")
+        public boolean autoconfirm = false;
+
+        protected abstract void doCall(JcloudsLocation loc, String indent) 
throws Exception;
+        
+        @Override
+        public Void call() throws Exception {
+            LocalManagementContext mgmt = new LocalManagementContext();
+            List<JcloudsLocation> locs = Lists.newArrayList();
+            try {
+                if (location != null && allLocations) {
+                    throw new FatalConfigurationRuntimeException("Must not 
specify --location and --all-locations");
+                } else if (location != null) {
+                    JcloudsLocation loc = (JcloudsLocation) 
mgmt.getLocationRegistry().resolve(location);
+                    locs.add(loc);
+                } else if (allLocations) {
+                    // Find all named locations that point at different target 
clouds
+                    Map<String, LocationDefinition> definedLocations = 
mgmt.getLocationRegistry().getDefinedLocations();
+                    for (LocationDefinition locationDef : 
definedLocations.values()) {
+                        Location loc = 
mgmt.getLocationRegistry().resolve(locationDef);
+                        if (loc instanceof JcloudsLocation) {
+                            boolean found = false;
+                            for (JcloudsLocation existing : locs) {
+                                if (equalTargets(existing, (JcloudsLocation) 
loc)) {
+                                    found = true;
+                                    break;
+                                }
+                            }
+                            if (!found) {
+                                locs.add((JcloudsLocation) loc);
+                            }
+                        }
+                    }
+                } else {
+                    throw new FatalConfigurationRuntimeException("Must specify 
one of --location or --all-locations");
+                }
+                
+                for (JcloudsLocation loc : locs) {
+                    stdout.println("Location {");
+                    stdout.println("\tprovider: "+loc.getProvider());
+                    stdout.println("\tdisplayName: "+loc.getDisplayName());
+                    stdout.println("\tidentity: "+loc.getIdentity());
+                    if (loc.getEndpoint() != null) stdout.println("\tendpoint: 
"+loc.getEndpoint());
+                    if (loc.getRegion() != null) stdout.println("\tregion: 
"+loc.getRegion());
+
+                    try {
+                        doCall(loc, "\t");
+                    } finally {
+                        stdout.println("}");
+                    }
+                }
+            } finally {
+                mgmt.terminate();
+            }
+            return null;
+        }
+        
+        @Override
+        public ToStringHelper string() {
+            return super.string()
+                    .add("location", location);
+        }
+        
+        protected boolean equalTargets(JcloudsLocation loc1, JcloudsLocation 
loc2) {
+            return Objects.equal(loc1.getProvider(), loc2.getProvider())
+                    && Objects.equal(loc1.getIdentity(), loc2.getIdentity())
+                    && Objects.equal(loc1.getEndpoint(), loc2.getEndpoint())
+                    && Objects.equal(loc1.getRegion(), loc2.getRegion());
+        }
+        
+        
+        protected boolean confirm(String msg, String indent) throws Exception {
+            if (autoconfirm) {
+                stdout.println(indent+"Auto-confirmed: "+msg);
+                return true;
+            } else {
+                stdout.println(indent+"Enter y/n. Are you sure you want to 
"+msg);
+                int in = stdin.read();
+                boolean confirmed = (Character.toLowerCase(in) == 'y');
+                if (confirmed) {
+                    stdout.println(indent+"Confirmed; will "+msg);
+                } else {
+                    stdout.println(indent+"Declined; will not "+msg);
+                }
+                return confirmed;
+            }
+        }
+    }
+    
+    public static abstract class ComputeCommand extends JcloudsCommand {
+        protected abstract void doCall(ComputeService computeService, String 
indent) throws Exception;
+            
+        @Override
+        protected void doCall(JcloudsLocation loc, String indent) throws 
Exception {
+            ComputeService computeService = loc.getComputeService();
+            doCall(computeService, indent);
+        }
+    }
+
+    @Command(name = "list-instances", description = "")
+    public static class ComputeListInstancesCommand extends ComputeCommand {
+        @Override
+        protected void doCall(ComputeService computeService, String indent) 
throws Exception {
+            failIfArguments();
+            Set<? extends ComputeMetadata> instances = 
computeService.listNodes();
+            stdout.println(indent+"Instances {");
+            for (ComputeMetadata instance : instances) {
+                stdout.println(indent+"\t"+instance);
+            }
+            stdout.println(indent+"}");
+        }
+    }
+
+    @Command(name = "list-images", description = "")
+    public static class ComputeListImagesCommand extends ComputeCommand {
+        @Override
+        protected void doCall(ComputeService computeService, String indent) 
throws Exception {
+            failIfArguments();
+            Set<? extends Image> images = computeService.listImages();
+            stdout.println(indent+"Images {");
+            for (Image image : images) {
+                stdout.println(indent+"\t"+image);
+            }
+            stdout.println(indent+"}");
+        }
+    }
+    
+    @Command(name = "list-hardware-profiles", description = "")
+    public static class ComputeListHardwareProfilesCommand extends 
ComputeCommand {
+        @Override
+        protected void doCall(ComputeService computeService, String indent) 
throws Exception {
+            failIfArguments();
+            Set<? extends Hardware> hardware = 
computeService.listHardwareProfiles();
+            stdout.println(indent+"Hardware Profiles {");
+            for (Hardware image : hardware) {
+                stdout.println(indent+"\t"+image);
+            }
+            stdout.println(indent+"}");
+        }
+    }
+    
+    @Command(name = "get-image", description = "")
+    public static class ComputeGetImageCommand extends ComputeCommand {
+        @Override
+        protected void doCall(ComputeService computeService, String indent) 
throws Exception {
+            if (arguments.isEmpty()) {
+                throw new ParseException("Requires at least one image-id 
arguments");
+            }
+            
+            for (String imageId : arguments) {
+                Image image = computeService.getImage(imageId);
+                stdout.println(indent+"Image "+imageId+" {");
+                stdout.println(indent+"\t"+image);
+                stdout.println(indent+"}");
+            }
+        }
+        
+        @Override
+        public ToStringHelper string() {
+            return super.string()
+                    .add("imageIds", arguments);
+        }
+    }
+
+    @Command(name = "default-template", description = "")
+    public static class ComputeDefaultTemplateCommand extends JcloudsCommand {
+        @Override
+        protected void doCall(JcloudsLocation loc, String indent) throws 
Exception {
+            failIfArguments();
+            ComputeService computeService = loc.getComputeService();
+            
+            Template template = loc.buildTemplate(computeService, 
loc.config().getBag());
+            Image image = template.getImage();
+            Hardware hardware = template.getHardware();
+            org.jclouds.domain.Location location = template.getLocation();
+            TemplateOptions options = template.getOptions();
+            stdout.println(indent+"Default template {");
+            stdout.println(indent+"\tImage: "+image);
+            stdout.println(indent+"\tHardware: "+hardware);
+            stdout.println(indent+"\tLocation: "+location);
+            stdout.println(indent+"\tOptions: "+options);
+            stdout.println(indent+"}");
+        }
+    }
+    
+    @Command(name = "terminate-instances", description = "")
+    public static class ComputeTerminateInstancesCommand extends 
ComputeCommand {
+        @Override
+        protected void doCall(ComputeService computeService, String indent) 
throws Exception {
+            if (arguments.isEmpty()) {
+                throw new ParseException("Requires at least one instance-id 
arguments");
+            }
+            
+            for (String instanceId : arguments) {
+                NodeMetadata instance = 
computeService.getNodeMetadata(instanceId);
+                if (instance == null) {
+                    stderr.println(indent+"Cannot terminate instance; could 
not find "+instanceId);
+                } else {
+                    boolean confirmed = confirm(indent, "terminate 
"+instanceId+" ("+instance+")");
+                    if (confirmed) {
+                        computeService.destroyNode(instanceId);
+                    }
+                }
+            }
+        }
+        
+        @Override
+        public ToStringHelper string() {
+            return super.string()
+                    .add("instanceIds", arguments);
+        }
+    }
+
+    public static abstract class BlobstoreCommand extends JcloudsCommand {
+        protected abstract void doCall(BlobStore blobstore, String indent) 
throws Exception;
+
+        @Override
+        protected void doCall(JcloudsLocation loc, String indent) throws 
Exception {
+            String identity = 
checkNotNull(loc.getConfig(LocationConfigKeys.ACCESS_IDENTITY), "identity must 
not be null");
+            String credential = 
checkNotNull(loc.getConfig(LocationConfigKeys.ACCESS_CREDENTIAL), "credential 
must not be null");
+            String provider = 
checkNotNull(loc.getConfig(LocationConfigKeys.CLOUD_PROVIDER), "provider must 
not be null");
+            String endpoint = 
loc.getConfig(CloudLocationConfig.CLOUD_ENDPOINT);
+            
+            BlobStoreContext context = 
JcloudsUtil.newBlobstoreContext(provider, endpoint, identity, credential);
+            try {
+                BlobStore blobStore = context.getBlobStore();
+                doCall(blobStore, indent);
+            } finally {
+                context.close();
+            }
+        }
+    }
+    
+    @Command(name = "list-containers", description = "")
+    public static class BlobstoreListContainersCommand extends 
BlobstoreCommand {
+        @Override
+        protected void doCall(BlobStore blobstore, String indent) throws 
Exception {
+            failIfArguments();
+            Set<? extends StorageMetadata> containers = blobstore.list();
+            stdout.println(indent+"Containers {");
+            for (StorageMetadata container : containers) {
+                stdout.println(indent+"\t"+container);
+            }
+            stdout.println(indent+"}");
+        }
+    }
+
+    @Command(name = "list-container", description = "")
+    public static class BlobstoreListContainerCommand extends BlobstoreCommand 
{
+        @Override
+        protected void doCall(BlobStore blobStore, String indent) throws 
Exception {
+            if (arguments.isEmpty()) {
+                throw new ParseException("Requires at least one container-name 
arguments");
+            }
+            
+            for (String containerName : arguments) {
+                Set<? extends StorageMetadata> contents = 
blobStore.list(containerName);
+                stdout.println(indent+"Container "+containerName+" {");
+                for (StorageMetadata content : contents) {
+                    stdout.println(indent+"\t"+content);
+                }
+                stdout.println(indent+"}");
+            }
+        }
+        
+        @Override
+        public ToStringHelper string() {
+            return super.string()
+                    .add("containers", arguments);
+        }
+    }
+    
+    @Command(name = "blob", description = "")
+    public static class BlobstoreGetBlobCommand extends BlobstoreCommand {
+        @Option(name = { "--container" }, title = "list contents of a given 
container",
+                description = "")
+        public String container;
+
+        @Option(name = { "--blob" }, title = "retrieves the blog in the given 
container",
+                description = "")
+        public String blob;
+
+        @Override
+        protected void doCall(BlobStore blobStore, String indent) throws 
Exception {
+            failIfArguments();
+            Blob content = blobStore.getBlob(container, blob);
+            stdout.println(indent+"Blob "+container+" : " +blob +" {");
+            stdout.println(indent+"\tHeaders {");
+            for (Map.Entry<String, String> entry : 
content.getAllHeaders().entries()) {
+                stdout.println(indent+"\t\t"+entry.getKey() + " = " + 
entry.getValue());
+            }
+            stdout.println(indent+"\t}");
+            stdout.println(indent+"\tmetadata : "+content.getMetadata());
+            stdout.println(indent+"\tpayload : 
"+Streams.readFullyString(content.getPayload().openStream()));
+            stdout.println(indent+"}");
+        }
+        
+        @Override
+        public ToStringHelper string() {
+            return super.string()
+                    .add("container", container)
+                    .add("blob", blob);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4f9d4ce8/usage/cli/src/main/java/org/apache/brooklyn/cli/ItemLister.java
----------------------------------------------------------------------
diff --git a/usage/cli/src/main/java/org/apache/brooklyn/cli/ItemLister.java 
b/usage/cli/src/main/java/org/apache/brooklyn/cli/ItemLister.java
new file mode 100644
index 0000000..f67629e
--- /dev/null
+++ b/usage/cli/src/main/java/org/apache/brooklyn/cli/ItemLister.java
@@ -0,0 +1,272 @@
+/*
+ * 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.cli;
+
+import io.airlift.command.Command;
+import io.airlift.command.Option;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.basic.BrooklynObject;
+import org.apache.brooklyn.catalog.Catalog;
+import org.apache.brooklyn.cli.lister.ClassFinder;
+import org.apache.brooklyn.cli.lister.ItemDescriptors;
+import brooklyn.entity.Entity;
+import brooklyn.entity.proxying.ImplementedBy;
+import brooklyn.location.Location;
+import brooklyn.location.LocationResolver;
+import brooklyn.policy.Enricher;
+import brooklyn.policy.Policy;
+import brooklyn.util.ResourceUtils;
+import brooklyn.util.collections.MutableSet;
+import brooklyn.util.net.Urls;
+import brooklyn.util.os.Os;
+import brooklyn.util.text.Strings;
+import brooklyn.util.text.TemplateProcessor;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.google.common.base.Charsets;
+import com.google.common.base.Splitter;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+public class ItemLister {
+    
+    private static final Logger LOG = 
LoggerFactory.getLogger(ItemLister.class);
+    private static final String BASE = "brooklyn/item-lister";
+    private static final String BASE_TEMPLATES = BASE+"/"+"templates";
+    private static final String BASE_STATICS = BASE+"/"+"statics";
+
+    @Command(name = "list-objects", description = "List Brooklyn objects 
(Entities, Policies, Enrichers and Locations)")
+    public static class ListAllCommand extends 
AbstractMain.BrooklynCommandCollectingArgs {
+
+        @Option(name = { "--jars" }, title = "Jars", description = "Jars to 
scan. If a file (not a url) pointing at a directory, will include all files in 
that directory")
+        public List<String> jars = Lists.newLinkedList();
+
+        @Option(name = { "--type-regex" }, title = "Regex for types to list")
+        public String typeRegex;
+
+        @Option(name = { "--catalog-only" }, title = "Whether to only list 
items annotated with @Catalog")
+        public boolean catalogOnly = true;
+
+        @Option(name = { "--ignore-impls" }, title = "Ignore Entity 
implementations, where there is an Entity interface with @ImplementedBy")
+        public boolean ignoreImpls = false;
+
+        @Option(name = { "--headings-only" }, title = "Whether to only show 
name/type, and not config keys etc")
+        public boolean headingsOnly = false;
+        
+        @Option(name = { "--output-folder" }, title = "Folder to save output")
+        public String outputFolder;
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public Void call() throws Exception {
+            List<URL> urls = getUrls();
+            LOG.info("Retrieving objects from "+urls);
+
+            // TODO Remove duplication from separate ListPolicyCommand etc
+            List<Class<? extends Entity>> entityTypes = getTypes(urls, 
Entity.class);
+            List<Class<? extends Policy>> policyTypes = getTypes(urls, 
Policy.class);
+            List<Class<? extends Enricher>> enricherTypes = getTypes(urls, 
Enricher.class);
+            List<Class<? extends Location>> locationTypes = getTypes(urls, 
Location.class, Boolean.FALSE);
+
+            Map<String, Object> result = ImmutableMap.<String, Object>builder()
+                    .put("entities", 
ItemDescriptors.toItemDescriptors(entityTypes, headingsOnly, "name"))
+                    .put("policies", 
ItemDescriptors.toItemDescriptors(policyTypes, headingsOnly, "name"))
+                    .put("enrichers", 
ItemDescriptors.toItemDescriptors(enricherTypes, headingsOnly, "name"))
+                    .put("locations", 
ItemDescriptors.toItemDescriptors(locationTypes, headingsOnly, "type"))
+                    .put("locationResolvers", 
ItemDescriptors.toItemDescriptors(ImmutableList.copyOf(ServiceLoader.load(LocationResolver.class)),
 true))
+                    .build();
+
+            String json = toJson(result);
+
+            if (outputFolder == null) {
+                System.out.println(json);
+            } else {
+                LOG.info("Outputting item list (size "+itemCount+") to " + 
outputFolder);
+                String outputPath = Os.mergePaths(outputFolder, "index.html");
+                String parentDir = (new 
File(outputPath).getParentFile()).getAbsolutePath();
+                mkdir(parentDir, "entities");
+                mkdir(parentDir, "policies");
+                mkdir(parentDir, "enrichers");
+                mkdir(parentDir, "locations");
+                mkdir(parentDir, "locationResolvers"); //TODO nothing written 
here yet...
+                
+                mkdir(parentDir, "style");
+                mkdir(Os.mergePaths(parentDir, "style"), "js");
+                mkdir(Os.mergePaths(parentDir, "style", "js"), "catalog");
+                
+                Files.write("var items = " + json, new 
File(Os.mergePaths(outputFolder, "items.js")), Charsets.UTF_8);
+                ResourceUtils resourceUtils = ResourceUtils.create(this);
+                
+                // root - just loads the above JSON
+                
copyFromItemListerClasspathBaseStaticsToOutputDir(resourceUtils, 
"brooklyn-object-list.html", "index.html");
+                
+                // statics - structure mirrors docs (not for any real reason 
however... the json is usually enough for our docs)
+                
copyFromItemListerClasspathBaseStaticsToOutputDir(resourceUtils, "common.js");
+                
copyFromItemListerClasspathBaseStaticsToOutputDir(resourceUtils, "items.css");
+                
copyFromItemListerClasspathBaseStaticsToOutputDir(resourceUtils, 
"style/js/underscore-min.js");
+                
copyFromItemListerClasspathBaseStaticsToOutputDir(resourceUtils, 
"style/js/underscore-min.map");
+                
copyFromItemListerClasspathBaseStaticsToOutputDir(resourceUtils, 
"style/js/catalog/bloodhound.js");
+
+                // now make pages for each item
+                
+                List<Map<String, Object>> entities = (List<Map<String, 
Object>>) result.get("entities");
+                String entityTemplateHtml = 
resourceUtils.getResourceAsString(Urls.mergePaths(BASE_TEMPLATES, 
"entity.html"));
+                for (Map<String, Object> entity : entities) {
+                    String type = (String) entity.get("type");
+                    String name = (String) entity.get("name");
+                    String entityHtml = 
TemplateProcessor.processTemplateContents(entityTemplateHtml, 
ImmutableMap.of("type", type, "name", name));
+                    Files.write(entityHtml, new 
File(Os.mergePaths(outputFolder, "entities", type + ".html")), Charsets.UTF_8);
+                }
+                
+                List<Map<String, Object>> policies = (List<Map<String, 
Object>>) result.get("policies");
+                String policyTemplateHtml = 
resourceUtils.getResourceAsString(Urls.mergePaths(BASE_TEMPLATES, 
"policy.html"));
+                for (Map<String, Object> policy : policies) {
+                    String type = (String) policy.get("type");
+                    String name = (String) policy.get("name");
+                    String policyHtml = 
TemplateProcessor.processTemplateContents(policyTemplateHtml, 
ImmutableMap.of("type", type, "name", name));
+                    Files.write(policyHtml, new 
File(Os.mergePaths(outputFolder, "policies", type + ".html")), Charsets.UTF_8);
+                }
+                
+                List<Map<String, Object>> enrichers = (List<Map<String, 
Object>>) result.get("enrichers");
+                String enricherTemplateHtml = 
resourceUtils.getResourceAsString(Urls.mergePaths(BASE_TEMPLATES, 
"enricher.html"));
+                for (Map<String, Object> enricher : enrichers) {
+                    String type = (String) enricher.get("type");
+                    String name = (String) enricher.get("name");
+                    String enricherHtml = 
TemplateProcessor.processTemplateContents(enricherTemplateHtml, 
ImmutableMap.of("type", type, "name", name));
+                    Files.write(enricherHtml, new 
File(Os.mergePaths(outputFolder, "enrichers", type + ".html")), Charsets.UTF_8);
+                }
+                
+                List<Map<String, Object>> locations = (List<Map<String, 
Object>>) result.get("locations");
+                String locationTemplateHtml = 
resourceUtils.getResourceAsString(Urls.mergePaths(BASE_TEMPLATES, 
"location.html"));
+                for (Map<String, Object> location : locations) {
+                    String type = (String) location.get("type");
+                    String locationHtml = 
TemplateProcessor.processTemplateContents(locationTemplateHtml, 
ImmutableMap.of("type", type));
+                    Files.write(locationHtml, new 
File(Os.mergePaths(outputFolder, "locations", type + ".html")), Charsets.UTF_8);
+                }
+                LOG.info("Finished outputting item list to " + outputFolder);
+            }
+            return null;
+        }
+
+        private void 
copyFromItemListerClasspathBaseStaticsToOutputDir(ResourceUtils resourceUtils, 
String item) throws IOException {
+            copyFromItemListerClasspathBaseStaticsToOutputDir(resourceUtils, 
item, item);
+        }
+        private void 
copyFromItemListerClasspathBaseStaticsToOutputDir(ResourceUtils resourceUtils, 
String item, String dest) throws IOException {
+            String js = 
resourceUtils.getResourceAsString(Urls.mergePaths(BASE_STATICS, item));
+            Files.write(js, new File(Os.mergePaths(outputFolder, dest)), 
Charsets.UTF_8);
+        }
+
+        private void mkdir(String rootDir, String dirName) {
+            (new File(Os.mergePaths(rootDir, dirName))).mkdirs();
+        }
+
+        protected List<URL> getUrls() throws MalformedURLException {
+            List<URL> urls = Lists.newArrayList();
+            if (jars.isEmpty()) {
+                String classpath = System.getenv("INITIAL_CLASSPATH");
+                if (Strings.isNonBlank(classpath)) {
+                    List<String> entries = 
Splitter.on(":").omitEmptyStrings().trimResults().splitToList(classpath);
+                    for (String entry : entries) {
+                        if (entry.endsWith(".jar") || entry.endsWith("/*")) {
+                            
urls.addAll(ClassFinder.toJarUrls(entry.replace("/*", "")));
+                        }
+                    }
+                } else {
+                    throw new IllegalArgumentException("No Jars to process");
+                }
+            } else {
+                for (String jar : jars) {
+                    List<URL> expanded = ClassFinder.toJarUrls(jar);
+                    if (expanded.isEmpty())
+                        LOG.warn("No jars found at: "+jar);
+                    urls.addAll(expanded);
+                }
+            }
+            return urls;
+        }
+
+        private <T extends BrooklynObject> List<Class<? extends T>> 
getTypes(List<URL> urls, Class<T> type) {
+            return getTypes(urls, type, null);
+        }
+
+        int itemCount = 0;
+        
+        private <T extends BrooklynObject> List<Class<? extends T>> 
getTypes(List<URL> urls, Class<T> type, Boolean catalogOnlyOverride) {
+            FluentIterable<Class<? extends T>> fluent = 
FluentIterable.from(ClassFinder.findClasses(urls, type));
+            if (typeRegex != null) {
+                fluent = 
fluent.filter(ClassFinder.withClassNameMatching(typeRegex));
+            }
+            if (catalogOnlyOverride == null ? catalogOnly : 
catalogOnlyOverride) {
+                fluent = 
fluent.filter(ClassFinder.withAnnotation(Catalog.class));
+            }
+            List<Class<? extends T>> filtered = fluent.toList();
+            Collection<Class<? extends T>> result;
+            if (ignoreImpls) {
+                result = MutableSet.copyOf(filtered);
+                for (Class<? extends T> clazz : filtered) {
+                    ImplementedBy implementedBy = 
clazz.getAnnotation(ImplementedBy.class);
+                    if (implementedBy != null) {
+                        result.remove(implementedBy.value());
+                    }
+                }
+            } else {
+                result = filtered;
+            }
+            itemCount += result.size();
+            return ImmutableList.copyOf(result);
+        }
+        
+        private String toJson(Object obj) throws JsonProcessingException {
+            ObjectMapper objectMapper = new ObjectMapper()
+                    .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
+                    .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
+                    .enable(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS)
+                    .enable(SerializationFeature.INDENT_OUTPUT)
+                    
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
+                    .setSerializationInclusion(JsonInclude.Include.ALWAYS)
+            
+                    // Only serialise annotated fields
+                    .setVisibility(PropertyAccessor.ALL, 
JsonAutoDetect.Visibility.NONE)
+                    .setVisibility(PropertyAccessor.FIELD, 
JsonAutoDetect.Visibility.ANY);
+            
+            return objectMapper.writeValueAsString(obj);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4f9d4ce8/usage/cli/src/main/java/org/apache/brooklyn/cli/Main.java
----------------------------------------------------------------------
diff --git a/usage/cli/src/main/java/org/apache/brooklyn/cli/Main.java 
b/usage/cli/src/main/java/org/apache/brooklyn/cli/Main.java
new file mode 100644
index 0000000..b8ced77
--- /dev/null
+++ b/usage/cli/src/main/java/org/apache/brooklyn/cli/Main.java
@@ -0,0 +1,986 @@
+/*
+ * 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.cli;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.Console;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+import brooklyn.BrooklynVersion;
+import brooklyn.basic.BrooklynTypes;
+import org.apache.brooklyn.catalog.BrooklynCatalog;
+import org.apache.brooklyn.catalog.CatalogItem;
+import org.apache.brooklyn.catalog.CatalogItem.CatalogItemType;
+import brooklyn.catalog.internal.CatalogInitialization;
+import org.apache.brooklyn.cli.CloudExplorer.BlobstoreGetBlobCommand;
+import org.apache.brooklyn.cli.CloudExplorer.BlobstoreListContainerCommand;
+import org.apache.brooklyn.cli.CloudExplorer.BlobstoreListContainersCommand;
+import org.apache.brooklyn.cli.CloudExplorer.ComputeDefaultTemplateCommand;
+import org.apache.brooklyn.cli.CloudExplorer.ComputeGetImageCommand;
+import 
org.apache.brooklyn.cli.CloudExplorer.ComputeListHardwareProfilesCommand;
+import org.apache.brooklyn.cli.CloudExplorer.ComputeListImagesCommand;
+import org.apache.brooklyn.cli.CloudExplorer.ComputeListInstancesCommand;
+import org.apache.brooklyn.cli.CloudExplorer.ComputeTerminateInstancesCommand;
+import org.apache.brooklyn.cli.ItemLister.ListAllCommand;
+import brooklyn.entity.Application;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.AbstractApplication;
+import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.StartableApplication;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.rebind.persister.BrooklynPersistenceUtils;
+import brooklyn.entity.rebind.persister.PersistMode;
+import brooklyn.entity.rebind.transformer.CompoundTransformer;
+import brooklyn.entity.trait.Startable;
+import brooklyn.launcher.BrooklynLauncher;
+import brooklyn.launcher.BrooklynServerDetails;
+import brooklyn.launcher.config.StopWhichAppsOnShutdown;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.Task;
+import brooklyn.management.ha.HighAvailabilityMode;
+import brooklyn.management.ha.OsgiManager;
+import brooklyn.rest.security.PasswordHasher;
+import brooklyn.rest.util.ShutdownHandler;
+import brooklyn.util.ResourceUtils;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.exceptions.FatalConfigurationRuntimeException;
+import brooklyn.util.exceptions.FatalRuntimeException;
+import brooklyn.util.exceptions.UserFacingException;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.javalang.Enums;
+import brooklyn.util.net.Networking;
+import brooklyn.util.text.Identifiers;
+import brooklyn.util.text.StringEscapes.JavaStringEscapes;
+import brooklyn.util.text.Strings;
+import brooklyn.util.time.Duration;
+import groovy.lang.GroovyClassLoader;
+import groovy.lang.GroovyShell;
+import io.airlift.command.Cli;
+import io.airlift.command.Cli.CliBuilder;
+import io.airlift.command.Command;
+import io.airlift.command.Option;
+
+/**
+ * This class is the primary CLI for brooklyn.
+ * Run with the `help` argument for help.
+ * <p>
+ * This class is designed for subclassing, with subclasses typically:
+ * <li> providing their own static {@link #main(String...)} (of course) which 
need simply invoke 
+ *      {@link #execCli(String[])} with the arguments 
+ * <li> returning their CLI name (e.g. "start.sh") in an overridden {@link 
#cliScriptName()}
+ * <li> providing an overridden {@link LaunchCommand} via {@link 
#cliLaunchCommand()} if desired
+ * <li> providing any other CLI customisations by overriding {@link 
#cliBuilder()}
+ *      (typically calling the parent and then customizing the builder)
+ * <li> populating a custom catalog using {@link 
LaunchCommand#populateCatalog(BrooklynCatalog)}
+ */
+public class Main extends AbstractMain {
+
+    private static final Logger log = LoggerFactory.getLogger(Main.class);
+
+    public static void main(String... args) {
+        log.debug("Launching Brooklyn via CLI, with "+Arrays.toString(args));
+        BrooklynVersion.INSTANCE.logSummary();
+        new Main().execCli(args);
+    }
+
+    @Command(name = "generate-password", description = "Generates a hashed 
web-console password")
+    public static class GeneratePasswordCommand extends 
BrooklynCommandCollectingArgs {
+
+        @Option(name = { "--user" }, title = "username", required = true)
+        public String user;
+
+        @Option(name = { "--stdin" }, title = "read password from stdin, 
instead of console", 
+                description = "Before using stdin, read 
http://stackoverflow.com/a/715681/1393883 for discussion of security!")
+        public boolean useStdin;
+
+        @Override
+        public Void call() throws Exception {
+            checkCanReadPassword();
+            
+            System.out.print("Enter password: ");
+            System.out.flush();
+            String password = readPassword();
+            if (Strings.isBlank(password)) {
+                throw new UserFacingException("Password must not be blank; 
aborting");
+            }
+            
+            System.out.print("Re-enter password: ");
+            System.out.flush();
+            String password2 = readPassword();
+            if (!password.equals(password2)) {
+                throw new UserFacingException("Passwords did not match; 
aborting");
+            }
+
+            String salt = Identifiers.makeRandomId(4);
+            String sha256password = PasswordHasher.sha256(salt, new 
String(password));
+            
+            System.out.println();
+            System.out.println("Please add the following to your 
brooklyn.properties:");
+            System.out.println();
+            System.out.println("brooklyn.webconsole.security.users="+user);
+            
System.out.println("brooklyn.webconsole.security.user."+user+".salt="+salt);
+            
System.out.println("brooklyn.webconsole.security.user."+user+".sha256="+sha256password);
+
+            return null;
+        }
+        
+        private void checkCanReadPassword() {
+            if (useStdin) {
+                // yes; always
+            } else {
+                Console console = System.console();
+                if (console == null) {
+                    throw new FatalConfigurationRuntimeException("No console; 
cannot get password securely; aborting");
+                }
+            }
+        }
+        
+        private String readPassword() throws IOException {
+            if (useStdin) {
+                return readLine(System.in);
+            } else {
+                return new String(System.console().readPassword());
+            }
+        }
+        
+        private String readLine(InputStream in) throws IOException {
+            StringBuilder result = new StringBuilder();
+            char c;
+            while ((c = (char)in.read()) != '\n') {
+                result.append(c);
+            }
+            return result.toString();
+        }
+    }
+    
+    @Command(name = "launch", description = "Starts a server, optionally with 
applications")
+    public static class LaunchCommand extends BrooklynCommandCollectingArgs {
+
+        @Option(name = { "--localBrooklynProperties" }, title = "local 
brooklyn.properties file",
+                description = "Load the given properties file, specific to 
this launch (appending to and overriding global properties)")
+        public String localBrooklynProperties;
+
+        @Option(name = { "--noGlobalBrooklynProperties" }, title = "do not use 
any global brooklyn.properties file found",
+            description = "Do not use the default global brooklyn.properties 
file found")
+        public boolean noGlobalBrooklynProperties = false;
+
+        @Option(name = { "-a", "--app" }, title = "application class or file",
+                description = "The Application to start. " +
+                        "For example, my.AppName, file://my/app.yaml, or 
classpath://my/AppName.groovy -- "
+                        + "note that a BROOKLYN_CLASSPATH environment variable 
may be required to "
+                        + "load classes from other locations")
+        public String app;
+
+        @Beta
+        @Option(name = { "-s", "--script" }, title = "script URI",
+                description = "EXPERIMENTAL. URI for a Groovy script to parse 
and load." +
+                        " This script will run before starting the app.")
+        public String script = null;
+
+        @Option(name = { "-l", "--location", "--locations" }, title = 
"location list",
+                description = "Specifies the locations where the application 
will be launched. " +
+                        "You can specify more than one location as a 
comma-separated list of values " +
+                        "(or as a JSON array, if the values are complex)")
+        public String locations;
+
+        @Option(name = { "--catalogInitial" }, title = "catalog initial bom 
URI",
+            description = "Specifies a catalog.bom URI to be used to populate 
the initial catalog, "
+                + "loaded on first run, or when persistence is off/empty or 
the catalog is reset")
+        public String catalogInitial;
+
+        @Option(name = { "--catalogReset" }, 
+            description = "Specifies that any catalog items which have been 
persisted should be cleared")
+        public boolean catalogReset;
+
+        @Option(name = { "--catalogAdd" }, title = "catalog bom URI to add",
+            description = "Specifies a catalog.bom to be added to the catalog")
+        public String catalogAdd;
+
+        @Option(name = { "--catalogForce" }, 
+            description = "Specifies that catalog items added via the CLI 
should be forcibly added, "
+                + "replacing any identical versions already registered (use 
with care!)")
+        public boolean catalogForce;
+
+        @Option(name = { "-p", "--port" }, title = "port number",
+                description = "Use this port for the brooklyn management web 
console and REST API; "
+                    + "default is 8081+ for http, 8443+ for https.")
+        public String port;
+
+        @Option(name = { "--https" },
+            description = "Launch the web console on https")
+        public boolean useHttps = false;
+        
+        @Option(name = { "-nc", "--noConsole" },
+                description = "Do not start the web console or REST API")
+        public boolean noConsole = false;
+
+        @Option(name = { "-b", "--bindAddress" },
+                description = "Specifies the IP address of the NIC to bind the 
Brooklyn Management Console to")
+        public String bindAddress = null;
+
+        @Option(name = { "-pa", "--publicAddress" },
+                description = "Specifies the IP address or hostname that the 
Brooklyn Management Console will be available on")
+        public String publicAddress = null;
+
+        @Option(name = { "--noConsoleSecurity" },
+                description = "Whether to disable authentication and security 
filters for the web console (for use when debugging on a secure network or 
bound to localhost)")
+        public Boolean noConsoleSecurity = false;
+
+        @Option(name = { "--startupContinueOnWebErrors" },
+            description = "Continue on web subsystem failures during startup "
+                + "(default is to abort if the web API fails to start, as 
management access is not normally possible)")
+        public boolean startupContinueOnWebErrors = false;
+
+        @Option(name = { "--startupFailOnPersistenceErrors" },
+            description = "Fail on persistence/HA subsystem failures during 
startup "
+                + "(default is to continue, so errors can be viewed via the 
API)")
+        public boolean startupFailOnPersistenceErrors = false;
+
+        @Option(name = { "--startupFailOnCatalogErrors" },
+            description = "Fail on catalog subsystem failures during startup "
+                + "(default is to continue, so errors can be viewed via the 
API)")
+        public boolean startupFailOnCatalogErrors = false;
+
+        @Option(name = { "--startupFailOnManagedAppsErrors" },
+            description = "Fail startup on errors deploying of managed apps 
specified via the command line "
+                + "(default is to continue, so errors can be viewed via the 
API)")
+        public boolean startupFailOnManagedAppsErrors = false;
+
+        @Beta
+        @Option(name = { "--startBrooklynNode" },
+                description = "Start a BrooklynNode entity representing this 
Brooklyn instance")
+        public boolean startBrooklynNode = false;
+
+        // Note in some cases, you can get 
java.util.concurrent.RejectedExecutionException
+        // if shutdown is not co-ordinated, looks like: {@linktourl 
https://gist.github.com/47066f72d6f6f79b953e}
+        @Beta
+        @Option(name = { "-sk", "--stopOnKeyPress" },
+                description = "Shutdown immediately on user text entry after 
startup (useful for debugging and demos)")
+        public boolean stopOnKeyPress = false;
+
+        final static String STOP_WHICH_APPS_ON_SHUTDOWN = "--stopOnShutdown";
+        protected final static String STOP_ALL = "all";
+        protected final static String STOP_ALL_IF_NOT_PERSISTED = 
"allIfNotPersisted";
+        protected final static String STOP_NONE = "none";
+        protected final static String STOP_THESE = "these";        
+        protected final static String STOP_THESE_IF_NOT_PERSISTED = 
"theseIfNotPersisted";
+        static { 
Enums.checkAllEnumeratedIgnoreCase(StopWhichAppsOnShutdown.class, STOP_ALL, 
STOP_ALL_IF_NOT_PERSISTED, STOP_NONE, STOP_THESE, STOP_THESE_IF_NOT_PERSISTED); 
}
+        
+        @Option(name = { STOP_WHICH_APPS_ON_SHUTDOWN },
+            allowedValues = { STOP_ALL, STOP_ALL_IF_NOT_PERSISTED, STOP_NONE, 
STOP_THESE, STOP_THESE_IF_NOT_PERSISTED },
+            description = "Which managed applications to stop on shutdown. 
Possible values are:\n"+
+                "all: stop all apps\n"+
+                "none: leave all apps running\n"+
+                "these: stop the apps explicitly started on this command line, 
but leave others started subsequently running\n"+
+                "theseIfNotPersisted: stop the apps started on this command 
line IF persistence is not enabled, otherwise leave all running\n"+
+                "allIfNotPersisted: stop all apps IF persistence is not 
enabled, otherwise leave all running")
+        public String stopWhichAppsOnShutdown = STOP_THESE_IF_NOT_PERSISTED;
+
+        @Option(name = { "--exitAndLeaveAppsRunningAfterStarting" },
+                description = "Once the application to start (from --app) is 
running exit the process, leaving any entities running. "
+                    + "Can be used in combination with --persist auto 
--persistenceDir <custom folder location> to attach to the running app at a 
later time.")
+        public boolean exitAndLeaveAppsRunningAfterStarting = false;
+
+        final static String PERSIST_OPTION = "--persist";
+        protected final static String PERSIST_OPTION_DISABLED = "disabled";
+        protected final static String PERSIST_OPTION_AUTO = "auto";
+        protected final static String PERSIST_OPTION_REBIND = "rebind";
+        protected final static String PERSIST_OPTION_CLEAN = "clean";
+        static { Enums.checkAllEnumeratedIgnoreCase(PersistMode.class, 
PERSIST_OPTION_DISABLED, PERSIST_OPTION_AUTO, PERSIST_OPTION_REBIND, 
PERSIST_OPTION_CLEAN); }
+        
+        // TODO currently defaults to disabled; want it to default to on, when 
we're ready
+        // TODO how to force a line-split per option?!
+        //      Looks like java.io.airlift.airline.UsagePrinter is splitting 
the description by word, and
+        //      wrapping it automatically.
+        //      See https://github.com/airlift/airline/issues/30
+        @Option(name = { PERSIST_OPTION }, 
+                allowedValues = { PERSIST_OPTION_DISABLED, 
PERSIST_OPTION_AUTO, PERSIST_OPTION_REBIND, PERSIST_OPTION_CLEAN },
+                title = "persistence mode",
+                description =
+                        "The persistence mode. Possible values are: \n"+
+                        "disabled: will not read or persist any state; \n"+
+                        "auto: will rebind to any existing state, or start up 
fresh if no state; \n"+
+                        "rebind: will rebind to the existing state, or fail if 
no state available; \n"+
+                        "clean: will start up fresh (removing any existing 
state)")
+        public String persist = PERSIST_OPTION_DISABLED;
+
+        @Option(name = { "--persistenceDir" }, title = "persistence dir",
+                description = "The directory to read/write persisted state (or 
container name if using an object store)")
+        public String persistenceDir;
+
+        @Option(name = { "--persistenceLocation" }, title = "persistence 
location",
+            description = "The location spec for an object store to read/write 
persisted state")
+        public String persistenceLocation;
+
+        final static String HA_OPTION = "--highAvailability";
+        protected final static String HA_OPTION_DISABLED = "disabled";
+        protected final static String HA_OPTION_AUTO = "auto";
+        protected final static String HA_OPTION_MASTER = "master";
+        protected final static String HA_OPTION_STANDBY = "standby";
+        protected final static String HA_OPTION_HOT_STANDBY = "hot_standby";
+        protected final static String HA_OPTION_HOT_BACKUP = "hot_backup";
+        static { 
Enums.checkAllEnumeratedIgnoreCase(HighAvailabilityMode.class, HA_OPTION_AUTO, 
HA_OPTION_DISABLED, HA_OPTION_MASTER, HA_OPTION_STANDBY, HA_OPTION_HOT_STANDBY, 
HA_OPTION_HOT_BACKUP); }
+        
+        @Option(name = { HA_OPTION }, allowedValues = { HA_OPTION_DISABLED, 
HA_OPTION_AUTO, HA_OPTION_MASTER, HA_OPTION_STANDBY, HA_OPTION_HOT_STANDBY, 
HA_OPTION_HOT_BACKUP },
+                title = "high availability mode",
+                description =
+                        "The high availability mode. Possible values are: \n"+
+                        "disabled: management node works in isolation - will 
not cooperate with any other standby/master nodes in management plane; \n"+
+                        "auto: will look for other management nodes, and will 
allocate itself as standby or master based on other nodes' states; \n"+
+                        "master: will startup as master - if there is already 
a master then fails immediately; \n"+
+                        "standby: will start up as lukewarm standby with no 
state - if there is not already a master then fails immediately, "
+                        + "and if there is a master which subsequently fails, 
this node can promote itself; \n"+
+                        "hot_standby: will start up as hot standby in 
read-only mode - if there is not already a master then fails immediately, "
+                        + "and if there is a master which subseuqently fails, 
this node can promote itself; \n"+
+                        "hot_backup: will start up as hot backup in read-only 
mode - no master is required, and this node will not become a master"
+                        )
+        public String highAvailability = HA_OPTION_AUTO;
+
+        @VisibleForTesting
+        protected ManagementContext explicitManagementContext;
+        
+        @Override
+        public Void call() throws Exception {
+            // Configure launcher
+            BrooklynLauncher launcher;
+            AppShutdownHandler shutdownHandler = new AppShutdownHandler();
+            failIfArguments();
+            try {
+                if (log.isDebugEnabled()) log.debug("Invoked launch command 
{}", this);
+                
+                if (!quiet) stdout.println(banner);
+    
+                if (verbose) {
+                    if (app != null) {
+                        stdout.println("Launching brooklyn app: " + app + " in 
" + locations);
+                    } else {
+                        stdout.println("Launching brooklyn server (no app)");
+                    }
+                }
+    
+                PersistMode persistMode = computePersistMode();
+                HighAvailabilityMode highAvailabilityMode = 
computeHighAvailabilityMode(persistMode);
+                
+                StopWhichAppsOnShutdown stopWhichAppsOnShutdownMode = 
computeStopWhichAppsOnShutdown();
+                
+                computeLocations();
+                
+                ResourceUtils utils = ResourceUtils.create(this);
+                GroovyClassLoader loader = new 
GroovyClassLoader(getClass().getClassLoader());
+    
+                // First, run a setup script if the user has provided one
+                if (script != null) {
+                    execGroovyScript(utils, loader, script);
+                }
+    
+                launcher = createLauncher();
+
+                CatalogInitialization catInit = new 
CatalogInitialization(catalogInitial, catalogReset, catalogAdd, catalogForce);
+                catInit.addPopulationCallback(new 
Function<CatalogInitialization,Void>() {
+                    @Override
+                    public Void apply(CatalogInitialization catInit) {
+                        try {
+                            
populateCatalog(catInit.getManagementContext().getCatalog());
+                        } catch (Throwable e) {
+                            catInit.handleException(e, "overridden main class 
populate catalog");
+                        }
+                        
+                        // Force load of catalog (so web console is up to date)
+                        confirmCatalog(catInit);
+                        return null;
+                    }
+                });
+                catInit.setFailOnStartupErrors(startupFailOnCatalogErrors);
+                launcher.catalogInitialization(catInit);
+                
+                launcher.persistMode(persistMode);
+                launcher.persistenceDir(persistenceDir);
+                launcher.persistenceLocation(persistenceLocation);
+
+                launcher.highAvailabilityMode(highAvailabilityMode);
+
+                launcher.stopWhichAppsOnShutdown(stopWhichAppsOnShutdownMode);
+                launcher.shutdownHandler(shutdownHandler);
+                
+                computeAndSetApp(launcher, utils, loader);
+                
+                customize(launcher);
+                
+            } catch (FatalConfigurationRuntimeException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new FatalConfigurationRuntimeException("Fatal error 
configuring Brooklyn launch: "+e.getMessage(), e);
+            }
+            
+            // Launch server
+            try {
+                launcher.start();
+            } catch (FatalRuntimeException e) {
+                // rely on caller logging this propagated exception
+                throw e;
+            } catch (Exception e) {
+                // for other exceptions we log it, possibly redundantly but 
better too much than too little
+                Exceptions.propagateIfFatal(e);
+                log.error("Error launching brooklyn: 
"+Exceptions.collapseText(e), e);
+                try {
+                    launcher.terminate();
+                } catch (Exception e2) {
+                    log.warn("Subsequent error during termination: "+e2);
+                    log.debug("Details of subsequent error during termination: 
"+e2, e2);
+                }
+                Exceptions.propagate(e);
+            }
+            
+            BrooklynServerDetails server = launcher.getServerDetails();
+            ManagementContext ctx = server.getManagementContext();
+            
+            if (verbose) {
+                Entities.dumpInfo(launcher.getApplications());
+            }
+            
+            if (!exitAndLeaveAppsRunningAfterStarting) {
+                waitAfterLaunch(ctx, shutdownHandler);
+            }
+
+            // will call mgmt.terminate() in BrooklynShutdownHookJob
+            return null;
+        }
+
+        /** can be overridden by subclasses which need to customize the 
launcher and/or management */
+        protected void customize(BrooklynLauncher launcher) {
+        }
+        
+        protected void computeLocations() {
+            boolean hasLocations = !Strings.isBlank(locations);
+            if (app != null) {
+                if (hasLocations && isYamlApp()) {
+                    log.info("YAML app combined with command line locations; 
YAML locations will take precedence; this behaviour may change in subsequent 
versions");
+                } else if (!hasLocations && isYamlApp()) {
+                    log.info("No locations supplied; defaulting to locations 
defined in YAML (if any)");
+                } else if (!hasLocations) {
+                    log.info("No locations supplied; starting with no 
locations");
+                }
+            } else if (hasLocations) {
+                log.error("Locations specified without any applications; 
ignoring locations");
+            }
+        }
+
+        protected boolean isYamlApp() {
+            return app != null && app.endsWith(".yaml");
+        }
+
+        protected PersistMode computePersistMode() {
+            Maybe<PersistMode> persistMode = 
Enums.valueOfIgnoreCase(PersistMode.class, persist);
+            if (!persistMode.isPresent()) {
+                if (Strings.isBlank(persist)) {
+                    throw new FatalConfigurationRuntimeException("Persist mode 
must not be blank");
+                } else {
+                    throw new FatalConfigurationRuntimeException("Illegal 
persist setting: "+persist);
+                }
+            }
+   
+            if (persistMode.get() == PersistMode.DISABLED) {
+                if (Strings.isNonBlank(persistenceDir))
+                    throw new FatalConfigurationRuntimeException("Cannot 
specify persistenceDir when persist is disabled");
+                if (Strings.isNonBlank(persistenceLocation))
+                    throw new FatalConfigurationRuntimeException("Cannot 
specify persistenceLocation when persist is disabled");
+            }
+            return persistMode.get();
+        }
+
+        protected HighAvailabilityMode computeHighAvailabilityMode(PersistMode 
persistMode) {
+            Maybe<HighAvailabilityMode> highAvailabilityMode = 
Enums.valueOfIgnoreCase(HighAvailabilityMode.class, highAvailability);
+            if (!highAvailabilityMode.isPresent()) {
+                if (Strings.isBlank(highAvailability)) {
+                    throw new FatalConfigurationRuntimeException("High 
availability mode must not be blank");
+                } else {
+                    throw new FatalConfigurationRuntimeException("Illegal 
highAvailability setting: "+highAvailability);
+                }
+            }
+   
+            if (highAvailabilityMode.get() != HighAvailabilityMode.DISABLED) {
+                if (persistMode == PersistMode.DISABLED) {
+                    if (highAvailabilityMode.get() == 
HighAvailabilityMode.AUTO)
+                        return HighAvailabilityMode.DISABLED;
+                    throw new FatalConfigurationRuntimeException("Cannot 
specify highAvailability when persistence is disabled");
+                } else if (persistMode == PersistMode.CLEAN && 
+                        (highAvailabilityMode.get() == 
HighAvailabilityMode.STANDBY 
+                        || highAvailabilityMode.get() == 
HighAvailabilityMode.HOT_STANDBY
+                        || highAvailabilityMode.get() == 
HighAvailabilityMode.HOT_BACKUP)) {
+                    throw new FatalConfigurationRuntimeException("Cannot 
specify highAvailability "+highAvailabilityMode.get()+" when persistence is 
CLEAN");
+                }
+            }
+            return highAvailabilityMode.get();
+        }
+        
+        protected StopWhichAppsOnShutdown computeStopWhichAppsOnShutdown() {
+            boolean isDefault = 
STOP_THESE_IF_NOT_PERSISTED.equals(stopWhichAppsOnShutdown);
+            if (exitAndLeaveAppsRunningAfterStarting && isDefault) {
+                return StopWhichAppsOnShutdown.NONE;
+            } else {
+                return Enums.valueOfIgnoreCase(StopWhichAppsOnShutdown.class, 
stopWhichAppsOnShutdown).get();
+            }
+        }
+        
+        @VisibleForTesting
+        /** forces the launcher to use the given management context, when 
programmatically invoked;
+         * mainly used when testing to inject a safe (and fast) mgmt context */
+        public void useManagementContext(ManagementContext mgmt) {
+            explicitManagementContext = mgmt;
+        }
+
+        protected BrooklynLauncher createLauncher() {
+            BrooklynLauncher launcher;
+            launcher = BrooklynLauncher.newInstance();
+            launcher.localBrooklynPropertiesFile(localBrooklynProperties)
+                    .ignorePersistenceErrors(!startupFailOnPersistenceErrors)
+                    .ignoreCatalogErrors(!startupFailOnCatalogErrors)
+                    .ignoreWebErrors(startupContinueOnWebErrors)
+                    .ignoreAppErrors(!startupFailOnManagedAppsErrors)
+                    .locations(Strings.isBlank(locations) ? 
ImmutableList.<String>of() : 
JavaStringEscapes.unwrapJsonishListIfPossible(locations));
+            
+            launcher.webconsole(!noConsole);
+            if (useHttps) {
+                // true sets it; false (not set) leaves it blank and falls 
back to config key
+                // (no way currently to override config key, but that could be 
added)
+                launcher.webconsoleHttps(useHttps);
+            }
+            launcher.webconsolePort(port);
+            
+            if (noGlobalBrooklynProperties) {
+                log.debug("Configuring to disable global brooklyn.properties");
+                launcher.globalBrooklynPropertiesFile(null);
+            }
+            if (noConsoleSecurity) {
+                log.info("Configuring to disable console security");
+                launcher.installSecurityFilter(false);
+            }
+            if (startBrooklynNode) {
+                log.info("Configuring BrooklynNode entity startup");
+                launcher.startBrooklynNode(true);
+            }
+            if (Strings.isNonEmpty(bindAddress)) {
+                log.debug("Configuring bind address as "+bindAddress);
+                
launcher.bindAddress(Networking.getInetAddressWithFixedName(bindAddress));
+            }
+            if (Strings.isNonEmpty(publicAddress)) {
+                log.debug("Configuring public address as "+publicAddress);
+                
launcher.publicAddress(Networking.getInetAddressWithFixedName(publicAddress));
+            }
+            if (explicitManagementContext!=null) {
+                log.debug("Configuring explicit management context 
"+explicitManagementContext);
+                launcher.managementContext(explicitManagementContext);
+            }
+            return launcher;
+        }
+
+        /** method intended for subclassing, to add custom items to the 
catalog */
+        protected void populateCatalog(BrooklynCatalog catalog) {
+            // nothing else added here
+        }
+
+        protected void confirmCatalog(CatalogInitialization catInit) {
+            // Force load of catalog (so web console is up to date)
+            Stopwatch time = Stopwatch.createStarted();
+            BrooklynCatalog catalog = 
catInit.getManagementContext().getCatalog();
+            Iterable<CatalogItem<Object, Object>> items = 
catalog.getCatalogItems();
+            for (CatalogItem<Object, Object> item: items) {
+                try {
+                    if (item.getCatalogItemType()==CatalogItemType.TEMPLATE) {
+                        // skip validation of templates, they might contain 
instructions,
+                        // and additionally they might contain multiple items 
in which case
+                        // the validation below won't work anyway (you need to 
go via a deployment plan)
+                    } else {
+                        Object spec = catalog.createSpec(item);
+                        if (spec instanceof EntitySpec) {
+                            
BrooklynTypes.getDefinedEntityType(((EntitySpec<?>)spec).getType());
+                        }
+                        log.debug("Catalog loaded spec "+spec+" for item 
"+item);
+                    }
+                } catch (Throwable throwable) {
+                    catInit.handleException(throwable, item);
+                }
+            }
+            log.debug("Catalog (size "+Iterables.size(items)+") confirmed in 
"+Duration.of(time));                      
+            // nothing else added here
+        }
+        
+        /** convenience for subclasses to specify that an app should run,
+         * throwing the right (caught) error if another app has already been 
specified */
+        protected void setAppToLaunch(String className) {
+            if (app!=null) {
+                if (app.equals(className)) return;
+                throw new FatalConfigurationRuntimeException("Cannot specify 
app '"+className+"' when '"+app+"' is already specified; "
+                    + "remove one or more conflicting CLI arguments.");
+            }
+            app = className;
+        }
+        
+        protected void computeAndSetApp(BrooklynLauncher launcher, 
ResourceUtils utils, GroovyClassLoader loader)
+            throws NoSuchMethodException, InvocationTargetException, 
IllegalAccessException, InstantiationException {
+            if (app != null) {
+                // Create the instance of the brooklyn app
+                log.debug("Loading the user's application: {}", app);
+   
+                if (isYamlApp()) {
+                    log.debug("Loading application as YAML spec: {}", app);
+                    String content = utils.getResourceAsString(app);
+                    launcher.application(content);
+                } else {
+                    Object loadedApp = 
loadApplicationFromClasspathOrParse(utils, loader, app);
+                    if (loadedApp instanceof ApplicationBuilder) {
+                        launcher.application((ApplicationBuilder)loadedApp);
+                    } else if (loadedApp instanceof Application) {
+                        launcher.application((AbstractApplication)loadedApp);
+                    } else {
+                        throw new 
FatalConfigurationRuntimeException("Unexpected application type 
"+(loadedApp==null ? null : loadedApp.getClass())+", for app "+loadedApp);
+                    }
+                }
+            }
+        }
+        
+        protected void waitAfterLaunch(ManagementContext ctx, 
AppShutdownHandler shutdownHandler) throws IOException {
+            if (stopOnKeyPress) {
+                // Wait for the user to type a key
+                log.info("Server started. Press return to stop.");
+                // Read in another thread so we can use timeout on the wait.
+                Task<Void> readTask = ctx.getExecutionManager().submit(new 
Callable<Void>() {
+                    @Override
+                    public Void call() throws Exception {
+                        stdin.read();
+                        return null;
+                    }
+                });
+                while (!shutdownHandler.isRequested()) {
+                    try {
+                        readTask.get(Duration.ONE_SECOND);
+                        break;
+                    } catch (TimeoutException e) {
+                        //check if there's a shutdown request
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                        throw Exceptions.propagate(e);
+                    } catch (ExecutionException e) {
+                        throw Exceptions.propagate(e);
+                    }
+                }
+                log.info("Shutting down applications.");
+                stopAllApps(ctx.getApplications());
+            } else {
+                // Block forever so that Brooklyn doesn't exit (until someone 
does cntrl-c or kill)
+                log.info("Launched Brooklyn; will now block until shutdown 
command received via GUI/API (recommended) or process interrupt.");
+                shutdownHandler.waitOnShutdownRequest();
+            }
+        }
+
+        protected void execGroovyScript(ResourceUtils utils, GroovyClassLoader 
loader, String script) {
+            log.debug("Running the user provided script: {}", script);
+            String content = utils.getResourceAsString(script);
+            GroovyShell shell = new GroovyShell(loader);
+            shell.evaluate(content);
+        }
+
+        /**
+         * Helper method that gets an instance of a brooklyn {@link 
AbstractApplication} or an {@link ApplicationBuilder}.
+         * Guaranteed to be non-null result of one of those types (throwing 
exception if app not appropriate).
+         */
+        @SuppressWarnings("unchecked")
+        protected Object loadApplicationFromClasspathOrParse(ResourceUtils 
utils, GroovyClassLoader loader, String app)
+                throws NoSuchMethodException, InvocationTargetException, 
IllegalAccessException, InstantiationException {
+            
+            Class<?> tempclazz;
+            log.debug("Loading application as class on classpath: {}", app);
+            try {
+                tempclazz = loader.loadClass(app, true, false);
+            } catch (ClassNotFoundException cnfe) { // Not a class on the 
classpath
+                log.debug("Loading \"{}\" as class on classpath failed, now 
trying as .groovy source file", app);
+                String content = utils.getResourceAsString(app);
+                tempclazz = loader.parseClass(content);
+            }
+            final Class<?> clazz = tempclazz;
+            
+            // Instantiate an app builder (wrapping app class in 
ApplicationBuilder, if necessary)
+            if (ApplicationBuilder.class.isAssignableFrom(clazz)) {
+                Constructor<?> constructor = clazz.getConstructor();
+                return (ApplicationBuilder) constructor.newInstance();
+            } else if (StartableApplication.class.isAssignableFrom(clazz)) {
+                EntitySpec<? extends StartableApplication> appSpec;
+                if (tempclazz.isInterface())
+                    appSpec = EntitySpec.create((Class<? extends 
StartableApplication>) clazz);
+                else
+                    appSpec = EntitySpec.create(StartableApplication.class, 
(Class<? extends StartableApplication>) clazz);
+                return new ApplicationBuilder(appSpec) {
+                    @Override protected void doBuild() {
+                    }};
+            } else if (AbstractApplication.class.isAssignableFrom(clazz)) {
+                // TODO If this application overrides init() then in trouble, 
as that won't get called!
+                // TODO grr; what to do about non-startable applications?
+                // without this we could return ApplicationBuilder rather than 
Object
+                Constructor<?> constructor = clazz.getConstructor();
+                return (AbstractApplication) constructor.newInstance();
+            } else if (AbstractEntity.class.isAssignableFrom(clazz)) {
+                // TODO Should we really accept any entity type, and just wrap 
it in an app? That's not documented!
+                return new ApplicationBuilder() {
+                    @Override protected void doBuild() {
+                        addChild(EntitySpec.create(Entity.class).impl((Class<? 
extends AbstractEntity>)clazz).additionalInterfaces(clazz.getInterfaces()));
+                    }};
+            } else if (Entity.class.isAssignableFrom(clazz)) {
+                return new ApplicationBuilder() {
+                    @Override protected void doBuild() {
+                        addChild(EntitySpec.create((Class<? extends 
Entity>)clazz));
+                    }};
+            } else {
+                throw new FatalConfigurationRuntimeException("Application 
class "+clazz+" must extend one of ApplicationBuilder or AbstractApplication");
+            }
+        }
+
+        @VisibleForTesting
+        protected void stopAllApps(Collection<? extends Application> 
applications) {
+            for (Application application : applications) {
+                try {
+                    if (application instanceof Startable) {
+                        ((Startable)application).stop();
+                    }
+                } catch (Exception e) {
+                    log.error("Error stopping "+application+": "+e, e);
+                }
+            }
+        }
+        
+        @Override
+        public ToStringHelper string() {
+            return super.string()
+                    .add("app", app)
+                    .add("script", script)
+                    .add("location", locations)
+                    .add("port", port)
+                    .add("bindAddress", bindAddress)
+                    .add("noConsole", noConsole)
+                    .add("noConsoleSecurity", noConsoleSecurity)
+                    .add("startupFailOnPersistenceErrors", 
startupFailOnPersistenceErrors)
+                    .add("startupFailsOnCatalogErrors", 
startupFailOnCatalogErrors)
+                    .add("startupContinueOnWebErrors", 
startupContinueOnWebErrors)
+                    .add("startupFailOnManagedAppsErrors", 
startupFailOnManagedAppsErrors)
+                    .add("catalogInitial", catalogInitial)
+                    .add("catalogAdd", catalogAdd)
+                    .add("catalogReset", catalogReset)
+                    .add("catalogForce", catalogForce)
+                    .add("stopWhichAppsOnShutdown", stopWhichAppsOnShutdown)
+                    .add("stopOnKeyPress", stopOnKeyPress)
+                    .add("localBrooklynProperties", localBrooklynProperties)
+                    .add("persist", persist)
+                    .add("persistenceLocation", persistenceLocation)
+                    .add("persistenceDir", persistenceDir)
+                    .add("highAvailability", highAvailability)
+                    .add("exitAndLeaveAppsRunningAfterStarting", 
exitAndLeaveAppsRunningAfterStarting);
+        }
+    }
+
+    @Command(name = "copy-state", description = "Retrieves persisted state")
+    public static class CopyStateCommand extends BrooklynCommandCollectingArgs 
{
+
+        @Option(name = { "--localBrooklynProperties" }, title = "local 
brooklyn.properties file",
+                description = "local brooklyn.properties file, specific to 
this launch (appending to and overriding global properties)")
+        public String localBrooklynProperties;
+
+        @Option(name = { "--persistenceDir" }, title = "persistence dir",
+                description = "The directory to read persisted state (or 
container name if using an object store)")
+        public String persistenceDir;
+
+        @Option(name = { "--persistenceLocation" }, title = "persistence 
location",
+            description = "The location spec for an object store to read 
persisted state")
+        public String persistenceLocation;
+    
+        @Option(name = { "--destinationDir" }, required = true, title = 
"destination dir",
+                description = "The directory to copy persistence data to")
+            public String destinationDir;
+        
+        @Option(name = { "--destinationLocation" }, title = "persistence 
location",
+                description = "The location spec for an object store to copy 
data to")
+            public String destinationLocation;
+        
+        @Option(name = { "--transformations" }, title = "transformations",
+                description = "local transformations file, to be applied to 
the copy of the data before uploading it")
+        public String transformations;
+        
+        @Override
+        public Void call() throws Exception {
+            checkNotNull(destinationDir, "destinationDir"); // presumably 
because required=true this will never be null!
+            
+            // Configure launcher
+            BrooklynLauncher launcher;
+            failIfArguments();
+            try {
+                log.info("Retrieving and copying persisted state to 
"+destinationDir+(Strings.isBlank(destinationLocation) ? "" : " @ 
"+destinationLocation));
+                
+                if (!quiet) stdout.println(banner);
+    
+                PersistMode persistMode = PersistMode.AUTO;
+                HighAvailabilityMode highAvailabilityMode = 
HighAvailabilityMode.DISABLED;
+                
+                launcher = BrooklynLauncher.newInstance()
+                        .localBrooklynPropertiesFile(localBrooklynProperties)
+                        .brooklynProperties(OsgiManager.USE_OSGI, false)
+                        .persistMode(persistMode)
+                        .persistenceDir(persistenceDir)
+                        .persistenceLocation(persistenceLocation)
+                        .highAvailabilityMode(highAvailabilityMode);
+                
+            } catch (FatalConfigurationRuntimeException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new FatalConfigurationRuntimeException("Fatal error 
configuring Brooklyn launch: "+e.getMessage(), e);
+            }
+            
+            try {
+                launcher.copyPersistedState(destinationDir, 
destinationLocation, loadTransformer(transformations));
+            } catch (FatalRuntimeException e) {
+                // rely on caller logging this propagated exception
+                throw e;
+            } catch (Exception e) {
+                // for other exceptions we log it, possibly redundantly but 
better too much than too little
+                Exceptions.propagateIfFatal(e);
+                log.error("Error retrieving persisted state: 
"+Exceptions.collapseText(e), e);
+                Exceptions.propagate(e);
+            } finally {
+                try {
+                    launcher.terminate();
+                } catch (Exception e2) {
+                    log.warn("Subsequent error during termination: "+e2);
+                    log.debug("Details of subsequent error during termination: 
"+e2, e2);
+                }
+            }
+            
+            return null;
+        }
+
+        protected CompoundTransformer loadTransformer(String 
transformationsFileUrl) {
+            return 
BrooklynPersistenceUtils.loadTransformer(ResourceUtils.create(this), 
transformationsFileUrl);
+        }
+        
+        @Override
+        public ToStringHelper string() {
+            return super.string()
+                    .add("localBrooklynProperties", localBrooklynProperties)
+                    .add("persistenceLocation", persistenceLocation)
+                    .add("persistenceDir", persistenceDir)
+                    .add("destinationDir", destinationDir);
+        }
+    }
+
+    /** method intended for overriding when a different {@link Cli} is desired,
+     * or when the subclass wishes to change any of the arguments */
+    @SuppressWarnings("unchecked")
+    @Override
+    protected CliBuilder<BrooklynCommand> cliBuilder() {
+        CliBuilder<BrooklynCommand> builder = 
Cli.<BrooklynCommand>builder(cliScriptName())
+                .withDescription("Brooklyn Management Service")
+                .withDefaultCommand(cliDefaultInfoCommand())
+                .withCommands(
+                        HelpCommand.class,
+                        cliInfoCommand(),
+                        GeneratePasswordCommand.class,
+                        CopyStateCommand.class,
+                        ListAllCommand.class,
+                        cliLaunchCommand()
+                );
+
+        builder.withGroup("cloud-compute")
+                .withDescription("Access compute details of a given cloud")
+                .withDefaultCommand(HelpCommand.class)
+                .withCommands(
+                        ComputeListImagesCommand.class,
+                        ComputeListHardwareProfilesCommand.class,
+                        ComputeListInstancesCommand.class,
+                        ComputeGetImageCommand.class,
+                        ComputeDefaultTemplateCommand.class,
+                        ComputeTerminateInstancesCommand.class);
+
+        builder.withGroup("cloud-blobstore")
+                .withDescription("Access blobstore details of a given cloud")
+                .withDefaultCommand(HelpCommand.class)
+                .withCommands(
+                        BlobstoreListContainersCommand.class, 
+                        BlobstoreListContainerCommand.class,
+                        BlobstoreGetBlobCommand.class);
+
+        return builder;
+    }
+    
+    /** method intended for overriding when a custom {@link LaunchCommand} is 
being specified  */
+    protected Class<? extends BrooklynCommand> cliLaunchCommand() {
+        return LaunchCommand.class;
+    }
+    
+    /** method intended for overriding when a custom {@link InfoCommand} is 
being specified  */
+    protected Class<? extends BrooklynCommand> cliInfoCommand() {
+        return InfoCommand.class;
+    }
+    
+    /** method intended for overriding when a custom {@link InfoCommand} is 
being specified  */
+    protected Class<? extends BrooklynCommand> cliDefaultInfoCommand() {
+        return DefaultInfoCommand.class;
+    }
+    
+    public static class AppShutdownHandler implements ShutdownHandler {
+        private CountDownLatch lock = new CountDownLatch(1);
+
+        @Override
+        public void onShutdownRequest() {
+            lock.countDown();
+        }
+        
+        public boolean isRequested() {
+            return lock.getCount() == 0;
+        }
+
+        public void waitOnShutdownRequest() {
+            try {
+                lock.await();
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                return; // exit gracefully
+            }
+        }
+    }
+}

Reply via email to