You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by ha...@apache.org on 2015/08/07 03:09:49 UTC

[5/8] incubator-brooklyn git commit: brooklyn-launcher: add org.apache package prefix

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f58ef3e/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynLauncher.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynLauncher.java b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynLauncher.java
new file mode 100644
index 0000000..ac17aa1
--- /dev/null
+++ b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynLauncher.java
@@ -0,0 +1,1062 @@
+/*
+ * 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.launcher;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.StringReader;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeoutException;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer;
+import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynAssemblyTemplateInstantiator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Function;
+import com.google.common.base.Splitter;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import brooklyn.catalog.internal.CatalogInitialization;
+import brooklyn.config.BrooklynProperties;
+import brooklyn.config.BrooklynServerConfig;
+import brooklyn.config.BrooklynServerPaths;
+import brooklyn.config.ConfigKey;
+import brooklyn.config.ConfigPredicates;
+import brooklyn.entity.Application;
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.BrooklynShutdownHooks;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.basic.StartableApplication;
+import brooklyn.entity.brooklynnode.BrooklynNode;
+import brooklyn.entity.brooklynnode.LocalBrooklynNode;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.rebind.PersistenceExceptionHandler;
+import brooklyn.entity.rebind.PersistenceExceptionHandlerImpl;
+import brooklyn.entity.rebind.RebindManager;
+import brooklyn.entity.rebind.RebindManagerImpl;
+import brooklyn.entity.rebind.persister.BrooklynMementoPersisterToObjectStore;
+import brooklyn.entity.rebind.persister.BrooklynPersistenceUtils;
+import brooklyn.entity.rebind.persister.PersistMode;
+import brooklyn.entity.rebind.persister.PersistenceObjectStore;
+import brooklyn.entity.rebind.transformer.CompoundTransformer;
+import brooklyn.entity.trait.Startable;
+import org.apache.brooklyn.launcher.config.StopWhichAppsOnShutdown;
+import brooklyn.location.Location;
+import brooklyn.location.LocationSpec;
+import brooklyn.location.PortRange;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation.LocalhostMachine;
+import brooklyn.location.basic.PortRanges;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.ha.HighAvailabilityManager;
+import brooklyn.management.ha.HighAvailabilityManagerImpl;
+import brooklyn.management.ha.HighAvailabilityMode;
+import brooklyn.management.ha.ManagementNodeState;
+import brooklyn.management.ha.ManagementPlaneSyncRecord;
+import brooklyn.management.ha.ManagementPlaneSyncRecordPersister;
+import brooklyn.management.ha.ManagementPlaneSyncRecordPersisterToObjectStore;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.mementos.BrooklynMementoRawData;
+import brooklyn.rest.BrooklynWebConfig;
+import brooklyn.rest.filter.BrooklynPropertiesSecurityFilter;
+import brooklyn.rest.security.provider.BrooklynUserWithRandomPasswordSecurityProvider;
+import brooklyn.rest.util.ShutdownHandler;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.exceptions.FatalConfigurationRuntimeException;
+import brooklyn.util.exceptions.FatalRuntimeException;
+import brooklyn.util.exceptions.RuntimeInterruptedException;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.io.FileUtil;
+import brooklyn.util.net.Networking;
+import brooklyn.util.os.Os;
+import brooklyn.util.stream.Streams;
+import brooklyn.util.text.Strings;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
+import io.brooklyn.camp.CampPlatform;
+import io.brooklyn.camp.spi.AssemblyTemplate;
+import io.brooklyn.camp.spi.instantiate.AssemblyTemplateInstantiator;
+
+/**
+ * Example usage is:
+ *  * <pre>
+ * {@code
+ * BrooklynLauncher launcher = BrooklynLauncher.newInstance()
+ *     .application(new WebClusterDatabaseExample().appDisplayName("Web-cluster example"))
+ *     .location("localhost")
+ *     .start();
+ * 
+ * Entities.dumpInfo(launcher.getApplications());
+ * </pre>
+ */
+public class BrooklynLauncher {
+
+    private static final Logger LOG = LoggerFactory.getLogger(BrooklynLauncher.class);
+
+    /** Creates a configurable (fluent API) launcher for use starting the web console and Brooklyn applications. */
+    public static BrooklynLauncher newInstance() {
+        return new BrooklynLauncher();
+    }
+    
+    private final Map<String,Object> brooklynAdditionalProperties = Maps.newLinkedHashMap();
+    private BrooklynProperties brooklynProperties;
+    private ManagementContext managementContext;
+    
+    private final List<String> locationSpecs = new ArrayList<String>();
+    private final List<Location> locations = new ArrayList<Location>();
+
+    private final List<Application> appsToManage = new ArrayList<Application>();
+    private final List<ApplicationBuilder> appBuildersToManage = new ArrayList<ApplicationBuilder>();
+    private final List<String> yamlAppsToManage = new ArrayList<String>();
+    private final List<Application> apps = new ArrayList<Application>();
+    
+    private boolean startWebApps = true;
+    private boolean startBrooklynNode = false;
+    private PortRange port = null;
+    private Boolean useHttps = null;
+    private InetAddress bindAddress = null;
+    private InetAddress publicAddress = null;
+    private Map<String,String> webApps = new LinkedHashMap<String,String>();
+    private Map<String, ?> webconsoleFlags = Maps.newLinkedHashMap();
+    private Boolean skipSecurityFilter = null;
+    
+    private boolean ignoreWebErrors = false;
+    private boolean ignorePersistenceErrors = true;
+    private boolean ignoreCatalogErrors = true;
+    private boolean ignoreAppErrors = true;
+    
+    private StopWhichAppsOnShutdown stopWhichAppsOnShutdown = StopWhichAppsOnShutdown.THESE_IF_NOT_PERSISTED;
+    private ShutdownHandler shutdownHandler;
+    
+    private Function<ManagementContext,Void> customizeManagement = null;
+    private CatalogInitialization catalogInitialization = null;
+    
+    private PersistMode persistMode = PersistMode.DISABLED;
+    private HighAvailabilityMode highAvailabilityMode = HighAvailabilityMode.DISABLED;
+    private String persistenceDir;
+    private String persistenceLocation;
+    private Duration persistPeriod = Duration.ONE_SECOND;
+    // these default values come from config in HighAvailablilityManagerImpl
+    private Duration haHeartbeatTimeoutOverride = null;
+    private Duration haHeartbeatPeriodOverride = null;
+    
+    private volatile BrooklynWebServer webServer;
+    private CampPlatform campPlatform;
+
+    private boolean started;
+    private String globalBrooklynPropertiesFile = Os.mergePaths(Os.home(), ".brooklyn", "brooklyn.properties");
+    private String localBrooklynPropertiesFile;
+
+    public List<Application> getApplications() {
+        if (!started) throw new IllegalStateException("Cannot retrieve application until started");
+        return ImmutableList.copyOf(apps);
+    }
+    
+    public BrooklynServerDetails getServerDetails() {
+        if (!started) throw new IllegalStateException("Cannot retrieve server details until started");
+        return new BrooklynServerDetails(webServer, managementContext);
+    }
+    
+    /** 
+     * Specifies that the launcher should manage the given Brooklyn application.
+     * The application must not yet be managed. 
+     * The application will not be started as part of this call (callers can
+     * subsequently call {@link #start()} or {@link #getApplications()}.
+     * 
+     * @see #application(ApplicationBuilder)
+     */
+    public BrooklynLauncher application(Application app) {
+        if (Entities.isManaged(app)) throw new IllegalArgumentException("Application must not already be managed");
+        appsToManage.add(checkNotNull(app, "app"));
+        return this;
+    }
+
+    /** 
+     * Specifies that the launcher should build and manage the given Brooklyn application.
+     * The application must not yet be managed. 
+     * The application will not be started as part of this call (callers can
+     * subsequently call {@link #start()} or {@link #getApplications()}.
+     * 
+     * @see #application(Application)
+     */
+    public BrooklynLauncher application(ApplicationBuilder appBuilder) {
+        appBuildersToManage.add(checkNotNull(appBuilder, "appBuilder"));
+        return this;
+    }
+
+    /** 
+     * Specifies that the launcher should build and manage the Brooklyn application
+     * described by the given spec.
+     * The application will not be started as part of this call (callers can
+     * subsequently call {@link #start()} or {@link #getApplications()}.
+     * 
+     * @see #application(Application)
+     */
+    public BrooklynLauncher application(EntitySpec<? extends StartableApplication> appSpec) {
+        appBuildersToManage.add(new ApplicationBuilder(checkNotNull(appSpec, "appSpec")) {
+                @Override protected void doBuild() {
+                }});
+        return this;
+    }
+
+    /**
+     * Specifies that the launcher should build and manage the Brooklyn application
+     * described by the given YAML blueprint.
+     * The application will not be started as part of this call (callers can
+     * subsequently call {@link #start()} or {@link #getApplications()}.
+     *
+     * @see #application(Application)
+     */
+    public BrooklynLauncher application(String yaml) {
+        this.yamlAppsToManage.add(yaml);
+        return this;
+    }
+
+    /**
+     * Adds a location to be passed in on {@link #start()}, when that calls
+     * {@code application.start(locations)}.
+     */
+    public BrooklynLauncher location(Location location) {
+        locations.add(checkNotNull(location, "location"));
+        return this;
+    }
+
+    /**
+     * Give the spec of an application, to be created.
+     * 
+     * @see #location(Location)
+     */
+    public BrooklynLauncher location(String spec) {
+        locationSpecs.add(checkNotNull(spec, "spec"));
+        return this;
+    }
+    
+    public BrooklynLauncher locations(List<String> specs) {
+        locationSpecs.addAll(checkNotNull(specs, "specs"));
+        return this;
+    }
+
+    public BrooklynLauncher persistenceLocation(@Nullable String persistenceLocationSpec) {
+        persistenceLocation = persistenceLocationSpec;
+        return this;
+    }
+
+    public BrooklynLauncher globalBrooklynPropertiesFile(String file) {
+        globalBrooklynPropertiesFile = file;
+        return this;
+    }
+    
+    public BrooklynLauncher localBrooklynPropertiesFile(String file) {
+        localBrooklynPropertiesFile = file;
+        return this;
+    }
+    
+    /** 
+     * Specifies the management context this launcher should use. 
+     * If not specified a new one is created automatically.
+     */
+    public BrooklynLauncher managementContext(ManagementContext context) {
+        if (brooklynProperties != null) throw new IllegalStateException("Cannot set brooklynProperties and managementContext");
+        this.managementContext = context;
+        return this;
+    }
+
+    /**
+     * Specifies the brooklyn properties to be used. 
+     * Must not be set if managementContext is explicitly set.
+     */
+    public BrooklynLauncher brooklynProperties(BrooklynProperties brooklynProperties){
+        if (managementContext != null) throw new IllegalStateException("Cannot set brooklynProperties and managementContext");
+        if (this.brooklynProperties!=null && brooklynProperties!=null && this.brooklynProperties!=brooklynProperties)
+            LOG.warn("Brooklyn properties being reset in "+this+"; set null first if you wish to clear it", new Throwable("Source of brooklyn properties reset"));
+        this.brooklynProperties = brooklynProperties;
+        return this;
+    }
+    
+    /**
+     * Specifies a property to be added to the brooklyn properties
+     */
+    public BrooklynLauncher brooklynProperties(String field, Object value) {
+        brooklynAdditionalProperties.put(checkNotNull(field, "field"), value);
+        return this;
+    }
+    public <T> BrooklynLauncher brooklynProperties(ConfigKey<T> key, T value) {
+        return brooklynProperties(key.getName(), value);
+    }
+
+    /** 
+     * Specifies whether the launcher will start the Brooklyn web console 
+     * (and any additional webapps specified); default true.
+     */
+    public BrooklynLauncher webconsole(boolean startWebApps) {
+        this.startWebApps = startWebApps;
+        return this;
+    }
+
+    public BrooklynLauncher installSecurityFilter(Boolean val) {
+        this.skipSecurityFilter = val == null ? null : !val;
+        return this;
+    }
+
+    /** 
+     * As {@link #webconsolePort(PortRange)} taking a single port
+     */ 
+    public BrooklynLauncher webconsolePort(int port) {
+        return webconsolePort(PortRanges.fromInteger(port));
+    }
+
+    /**
+     * As {@link #webconsolePort(PortRange)} taking a string range
+     */
+    public BrooklynLauncher webconsolePort(String port) {
+        if (port==null) return webconsolePort((PortRange)null);
+        return webconsolePort(PortRanges.fromString(port));
+    }
+
+    /**
+     * Specifies the port where the web console (and any additional webapps specified) will listen;
+     * default (null) means "8081+" being the first available >= 8081 (or "8443+" for https).
+     */ 
+    public BrooklynLauncher webconsolePort(PortRange port) {
+        this.port = port;
+        return this;
+    }
+
+    /**
+     * Specifies whether the webconsole should use https.
+     */ 
+    public BrooklynLauncher webconsoleHttps(Boolean useHttps) {
+        this.useHttps = useHttps;
+        return this;
+    }
+
+    /**
+     * Specifies the NIC where the web console (and any additional webapps specified) will be bound;
+     * default 0.0.0.0, unless no security is specified (e.g. users) in which case it is localhost.
+     */ 
+    public BrooklynLauncher bindAddress(InetAddress bindAddress) {
+        this.bindAddress = bindAddress;
+        return this;
+    }
+
+    /**
+     * Specifies the address that the management context's REST API will be available on. Defaults
+     * to {@link #bindAddress} if it is not 0.0.0.0.
+     * @see #bindAddress(java.net.InetAddress)
+     */
+    public BrooklynLauncher publicAddress(InetAddress publicAddress) {
+        this.publicAddress = publicAddress;
+        return this;
+    }
+
+    /**
+     * Specifies additional flags to be passed to {@link BrooklynWebServer}.
+     */ 
+    public BrooklynLauncher webServerFlags(Map<String,?> webServerFlags) {
+        this.webconsoleFlags  = webServerFlags;
+        return this;
+    }
+
+    /** 
+     * Specifies an additional webapp to host on the webconsole port.
+     * @param contextPath The context path (e.g. "/hello", or equivalently just "hello") where the webapp will be hosted.
+     *      "/" will override the brooklyn console webapp.
+     * @param warUrl The URL from which the WAR should be loaded, supporting classpath:// protocol in addition to file:// and http(s)://.
+     */
+    public BrooklynLauncher webapp(String contextPath, String warUrl) {
+        webApps.put(contextPath, warUrl);
+        return this;
+    }
+
+    public BrooklynLauncher ignorePersistenceErrors(boolean ignorePersistenceErrors) {
+        this.ignorePersistenceErrors = ignorePersistenceErrors;
+        return this;
+    }
+
+    public BrooklynLauncher ignoreCatalogErrors(boolean ignoreCatalogErrors) {
+        this.ignoreCatalogErrors = ignoreCatalogErrors;
+        return this;
+    }
+
+    public BrooklynLauncher ignoreWebErrors(boolean ignoreWebErrors) {
+        this.ignoreWebErrors = ignoreWebErrors;
+        return this;
+    }
+
+    public BrooklynLauncher ignoreAppErrors(boolean ignoreAppErrors) {
+        this.ignoreAppErrors = ignoreAppErrors;
+        return this;
+    }
+
+    public BrooklynLauncher stopWhichAppsOnShutdown(StopWhichAppsOnShutdown stopWhich) {
+        this.stopWhichAppsOnShutdown = stopWhich;
+        return this;
+    }
+
+    public BrooklynLauncher customizeManagement(Function<ManagementContext,Void> customizeManagement) {
+        this.customizeManagement = customizeManagement;
+        return this;
+    }
+
+    @Beta
+    public BrooklynLauncher catalogInitialization(CatalogInitialization catInit) {
+        if (this.catalogInitialization!=null)
+            throw new IllegalStateException("Initial catalog customization already set.");
+        this.catalogInitialization = catInit;
+        return this;
+    }
+
+    public BrooklynLauncher shutdownOnExit(boolean val) {
+        LOG.warn("Call to deprecated `shutdownOnExit`", new Throwable("source of deprecated call"));
+        stopWhichAppsOnShutdown = StopWhichAppsOnShutdown.THESE_IF_NOT_PERSISTED;
+        return this;
+    }
+
+    public BrooklynLauncher persistMode(PersistMode persistMode) {
+        this.persistMode = persistMode;
+        return this;
+    }
+
+    public BrooklynLauncher highAvailabilityMode(HighAvailabilityMode highAvailabilityMode) {
+        this.highAvailabilityMode = highAvailabilityMode;
+        return this;
+    }
+
+    public BrooklynLauncher persistenceDir(@Nullable String persistenceDir) {
+        this.persistenceDir = persistenceDir;
+        return this;
+    }
+
+    public BrooklynLauncher persistenceDir(@Nullable File persistenceDir) {
+        if (persistenceDir==null) return persistenceDir((String)null);
+        return persistenceDir(persistenceDir.getAbsolutePath());
+    }
+
+    public BrooklynLauncher persistPeriod(Duration persistPeriod) {
+        this.persistPeriod = persistPeriod;
+        return this;
+    }
+
+    public BrooklynLauncher haHeartbeatTimeout(Duration val) {
+        this.haHeartbeatTimeoutOverride = val;
+        return this;
+    }
+
+    public BrooklynLauncher startBrooklynNode(boolean val) {
+        this.startBrooklynNode = val;
+        return this;
+    }
+
+    /**
+     * Controls both the frequency of heartbeats, and the frequency of checking the health of other nodes.
+     */
+    public BrooklynLauncher haHeartbeatPeriod(Duration val) {
+        this.haHeartbeatPeriodOverride = val;
+        return this;
+    }
+
+    /**
+     * @param destinationDir Directory for state to be copied to
+     */
+    public void copyPersistedState(String destinationDir) {
+        copyPersistedState(destinationDir, null, null);
+    }
+
+    /**
+     * A listener to call when the user requests a shutdown (i.e. through the REST API)
+     */
+    public BrooklynLauncher shutdownHandler(ShutdownHandler shutdownHandler) {
+        this.shutdownHandler = shutdownHandler;
+        return this;
+    }
+
+    /**
+     * @param destinationDir Directory for state to be copied to
+     * @param destinationLocation Optional location if target for copied state is a blob store.
+     */
+    public void copyPersistedState(String destinationDir, @Nullable String destinationLocation) {
+        copyPersistedState(destinationDir, destinationLocation, null);
+    }
+
+    /**
+     * @param destinationDir Directory for state to be copied to
+     * @param destinationLocationSpec Optional location if target for copied state is a blob store.
+     * @param transformer Optional transformations to apply to retrieved state before it is copied.
+     */
+    public void copyPersistedState(String destinationDir, @Nullable String destinationLocationSpec, @Nullable CompoundTransformer transformer) {
+        initManagementContext();
+        try {
+            highAvailabilityMode = HighAvailabilityMode.HOT_STANDBY;
+            initPersistence();
+        } catch (Exception e) {
+            handleSubsystemStartupError(ignorePersistenceErrors, "persistence", e);
+        }
+        
+        try {
+            BrooklynMementoRawData memento = managementContext.getRebindManager().retrieveMementoRawData();
+            if (transformer != null) memento = transformer.transform(memento);
+            
+            ManagementPlaneSyncRecord planeState = managementContext.getHighAvailabilityManager().loadManagementPlaneSyncRecord(true);
+            
+            LOG.info("Persisting state to "+destinationDir+(destinationLocationSpec!=null ? " @ "+destinationLocationSpec : ""));
+            PersistenceObjectStore destinationObjectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(
+                managementContext, destinationLocationSpec, destinationDir);
+            BrooklynPersistenceUtils.writeMemento(managementContext, memento, destinationObjectStore);
+            BrooklynPersistenceUtils.writeManagerMemento(managementContext, planeState, destinationObjectStore);
+
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            LOG.debug("Error copying persisted state (rethrowing): " + e, e);
+            throw new FatalRuntimeException("Error copying persisted state: " +
+                Exceptions.collapseText(e), e);
+        }
+    }
+
+    /** @deprecated since 0.7.0 use {@link #copyPersistedState} instead */
+    // Make private after deprecation
+    @Deprecated
+    public BrooklynMementoRawData retrieveState() {
+        initManagementContext();
+        initPersistence();
+        return managementContext.getRebindManager().retrieveMementoRawData();
+    }
+
+    /**
+     * @param memento The state to copy
+     * @param destinationDir Directory for state to be copied to
+     * @param destinationLocation Optional location if target for copied state is a blob store.
+     * @deprecated since 0.7.0 use {@link #copyPersistedState} instead
+     */
+    // Make private after deprecation
+    @Deprecated
+    public void persistState(BrooklynMementoRawData memento, String destinationDir, @Nullable String destinationLocationSpec) {
+        initManagementContext();
+        PersistenceObjectStore destinationObjectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(
+            managementContext, destinationLocationSpec, destinationDir);
+        BrooklynPersistenceUtils.writeMemento(managementContext, memento, destinationObjectStore);
+    }
+
+    /**
+     * Starts the web server (with web console) and Brooklyn applications, as per the specifications configured. 
+     * @return An object containing details of the web server and the management context.
+     */
+    public BrooklynLauncher start() {
+        if (started) throw new IllegalStateException("Cannot start() or launch() multiple times");
+        started = true;
+
+        // Create the management context
+        initManagementContext();
+
+        // Inform catalog initialization that it is starting up
+        CatalogInitialization catInit = ((ManagementContextInternal)managementContext).getCatalogInitialization();
+        catInit.setStartingUp(true);
+
+        // Start webapps as soon as mgmt context available -- can use them to detect progress of other processes
+        if (startWebApps) {
+            try {
+                startWebApps();
+            } catch (Exception e) {
+                handleSubsystemStartupError(ignoreWebErrors, "core web apps", e);
+            }
+        }
+        
+        // Add a CAMP platform
+        campPlatform = new BrooklynCampPlatformLauncherNoServer()
+                .useManagementContext(managementContext)
+                .launch()
+                .getCampPlatform();
+        // TODO start CAMP rest _server_ in the below (at /camp) ?
+
+        try {
+            initPersistence();
+            startPersistence();
+        } catch (Exception e) {
+            handleSubsystemStartupError(ignorePersistenceErrors, "persistence", e);
+        }
+
+        try {
+            // run cat init now if it hasn't yet been run; 
+            // will also run if there was an ignored error in catalog above, allowing it to fail startup here if requested
+            if (catInit!=null && !catInit.hasRunOfficialInitialization()) {
+                if (persistMode==PersistMode.DISABLED) {
+                    LOG.debug("Loading catalog as part of launch sequence (it was not loaded as part of any rebind sequence)");
+                    catInit.populateCatalog(ManagementNodeState.MASTER, true, true, null);
+                } else {
+                    // should have loaded during rebind
+                    ManagementNodeState state = managementContext.getHighAvailabilityManager().getNodeState();
+                    LOG.warn("Loading catalog for "+state+" as part of launch sequence (it was not loaded as part of the rebind sequence)");
+                    catInit.populateCatalog(state, true, true, null);
+                }
+            }
+        } catch (Exception e) {
+            handleSubsystemStartupError(ignoreCatalogErrors, "initial catalog", e);
+        }
+        catInit.setStartingUp(false);
+
+        // Create the locations. Must happen after persistence is started in case the
+        // management context's catalog is loaded from persisted state. (Location
+        // resolution uses the catalog's classpath to scan for resolvers.)
+        locations.addAll(managementContext.getLocationRegistry().resolve(locationSpecs));
+
+        // Already rebinded successfully, so previous apps are now available.
+        // Allow the startup to be visible in console for newly created apps.
+        ((LocalManagementContext)managementContext).noteStartupComplete();
+
+        // TODO create apps only after becoming master, analogously to catalog initialization
+        try {
+            createApps();
+            startApps();
+        } catch (Exception e) {
+            handleSubsystemStartupError(ignoreAppErrors, "brooklyn autostart apps", e);
+        }
+
+        if (startBrooklynNode) {
+            try {
+                startBrooklynNode();
+            } catch (Exception e) {
+                handleSubsystemStartupError(ignoreAppErrors, "brooklyn node / self entity", e);
+            }
+        }
+        
+        if (persistMode != PersistMode.DISABLED) {
+            // Make sure the new apps are persisted in case process exits immediately.
+            managementContext.getRebindManager().forcePersistNow(false, null);
+        }
+        return this;
+    }
+
+    private void initManagementContext() {
+        // Create the management context
+        if (managementContext == null) {
+            if (brooklynProperties == null) {
+                BrooklynProperties.Factory.Builder builder = BrooklynProperties.Factory.builderDefault();
+                if (globalBrooklynPropertiesFile != null) {
+                    if (fileExists(globalBrooklynPropertiesFile)) {
+                        LOG.debug("Using global properties file "+globalBrooklynPropertiesFile);
+                        // brooklyn.properties stores passwords (web-console and cloud credentials), 
+                        // so ensure it has sensible permissions
+                        checkFileReadable(globalBrooklynPropertiesFile);
+                        checkFilePermissionsX00(globalBrooklynPropertiesFile);
+                    } else {
+                        LOG.debug("Global properties file "+globalBrooklynPropertiesFile+" does not exist, will ignore");
+                    }
+                    builder.globalPropertiesFile(globalBrooklynPropertiesFile);
+                } else {
+                    LOG.debug("Global properties file disabled");
+                    builder.globalPropertiesFile(null);
+                }
+                
+                if (localBrooklynPropertiesFile != null) {
+                    checkFileReadable(localBrooklynPropertiesFile);
+                    checkFilePermissionsX00(localBrooklynPropertiesFile);
+                    builder.localPropertiesFile(localBrooklynPropertiesFile);
+                }
+                managementContext = new LocalManagementContext(builder, brooklynAdditionalProperties);
+            } else {
+                if (globalBrooklynPropertiesFile != null)
+                    LOG.warn("Ignoring globalBrooklynPropertiesFile "+globalBrooklynPropertiesFile+" because explicit brooklynProperties supplied");
+                if (localBrooklynPropertiesFile != null)
+                    LOG.warn("Ignoring localBrooklynPropertiesFile "+localBrooklynPropertiesFile+" because explicit brooklynProperties supplied");
+                managementContext = new LocalManagementContext(brooklynProperties, brooklynAdditionalProperties);
+            }
+            brooklynProperties = ((ManagementContextInternal)managementContext).getBrooklynProperties();
+            
+            // We created the management context, so we are responsible for terminating it
+            BrooklynShutdownHooks.invokeTerminateOnShutdown(managementContext);
+            
+        } else if (brooklynProperties == null) {
+            brooklynProperties = ((ManagementContextInternal)managementContext).getBrooklynProperties();
+            brooklynProperties.addFromMap(brooklynAdditionalProperties);
+        }
+        
+        if (catalogInitialization!=null) {
+            ((ManagementContextInternal)managementContext).setCatalogInitialization(catalogInitialization);
+        }
+        
+        if (customizeManagement!=null) {
+            customizeManagement.apply(managementContext);
+        }
+    }
+
+    private boolean fileExists(String file) {
+        return new File(Os.tidyPath(file)).exists();
+    }
+
+    private void checkFileReadable(String file) {
+        File f = new File(Os.tidyPath(file));
+        if (!f.exists()) {
+            throw new FatalRuntimeException("File "+file+" does not exist");
+        }
+        if (!f.isFile()) {
+            throw new FatalRuntimeException(file+" is not a file");
+        }
+        if (!f.canRead()) {
+            throw new FatalRuntimeException(file+" is not readable");
+        }
+    }
+    
+    private void checkFilePermissionsX00(String file) {
+        File f = new File(Os.tidyPath(file));
+        
+        Maybe<String> permission = FileUtil.getFilePermissions(f);
+        if (permission.isAbsent()) {
+            LOG.debug("Could not determine permissions of file; assuming ok: "+f);
+        } else {
+            if (!permission.get().subSequence(4, 10).equals("------")) {
+                throw new FatalRuntimeException("Invalid permissions for file "+file+"; expected ?00 but was "+permission.get());
+            }
+        }
+    }
+    
+    private void handleSubsystemStartupError(boolean ignoreSuchErrors, String system, Exception e) {
+        Exceptions.propagateIfFatal(e);
+        if (ignoreSuchErrors) {
+            LOG.error("Subsystem for "+system+" had startup error (continuing with startup): "+e, e);
+            if (managementContext!=null)
+                ((ManagementContextInternal)managementContext).errors().add(e);
+        } else {
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    protected void startWebApps() {
+        // No security options in properties and no command line options overriding.
+        if (Boolean.TRUE.equals(skipSecurityFilter) && bindAddress==null) {
+            LOG.info("Starting Brooklyn web-console on loopback because security is explicitly disabled and no bind address specified");
+            bindAddress = Networking.LOOPBACK;
+        } else if (BrooklynWebConfig.hasNoSecurityOptions(brooklynProperties)) {
+            if (bindAddress==null) {
+                LOG.info("Starting Brooklyn web-console with passwordless access on localhost and protected access from any other interfaces (no bind address specified)");
+            } else {
+                if (Arrays.equals(new byte[] { 127, 0, 0, 1 }, bindAddress.getAddress())) { 
+                    LOG.info("Starting Brooklyn web-console with passwordless access on localhost");
+                } else if (Arrays.equals(new byte[] { 0, 0, 0, 0 }, bindAddress.getAddress())) { 
+                    LOG.info("Starting Brooklyn web-console with passwordless access on localhost and random password (logged) required from any other interfaces");
+                } else { 
+                    LOG.info("Starting Brooklyn web-console with passwordless access on localhost (if permitted) and random password (logged) required from any other interfaces");
+                }
+            }
+            brooklynProperties.put(
+                    BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME,
+                    BrooklynUserWithRandomPasswordSecurityProvider.class.getName());
+        } else {
+            LOG.debug("Starting Brooklyn using security properties: "+brooklynProperties.submap(ConfigPredicates.startingWith(BrooklynWebConfig.BASE_NAME_SECURITY)).asMapWithStringKeys());
+        }
+        if (bindAddress == null) bindAddress = Networking.ANY_NIC;
+
+        LOG.debug("Starting Brooklyn web-console with bindAddress "+bindAddress+" and properties "+brooklynProperties);
+        try {
+            webServer = new BrooklynWebServer(webconsoleFlags, managementContext);
+            webServer.setBindAddress(bindAddress);
+            webServer.setPublicAddress(publicAddress);
+            if (port!=null) webServer.setPort(port);
+            if (useHttps!=null) webServer.setHttpsEnabled(useHttps);
+            webServer.setShutdownHandler(shutdownHandler);
+            webServer.putAttributes(brooklynProperties);
+            if (skipSecurityFilter != Boolean.TRUE) {
+                webServer.setSecurityFilter(BrooklynPropertiesSecurityFilter.class);
+            }
+            for (Map.Entry<String, String> webapp : webApps.entrySet()) {
+                webServer.addWar(webapp.getKey(), webapp.getValue());
+            }
+            webServer.start();
+
+        } catch (Exception e) {
+            LOG.warn("Failed to start Brooklyn web-console (rethrowing): " + Exceptions.collapseText(e));
+            throw new FatalRuntimeException("Failed to start Brooklyn web-console: " + Exceptions.collapseText(e), e);
+        }
+    }
+
+    protected void initPersistence() {
+        // Prepare the rebind directory, and initialise the RebindManager as required
+        final PersistenceObjectStore objectStore;
+        if (persistMode == PersistMode.DISABLED) {
+            LOG.info("Persistence disabled");
+            objectStore = null;
+            
+        } else {
+            try {
+                if (persistenceLocation == null) {
+                    persistenceLocation = brooklynProperties.getConfig(BrooklynServerConfig.PERSISTENCE_LOCATION_SPEC);
+                }
+                persistenceDir = BrooklynServerPaths.newMainPersistencePathResolver(brooklynProperties).location(persistenceLocation).dir(persistenceDir).resolve();
+                objectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(managementContext, persistenceLocation, persistenceDir, 
+                    persistMode, highAvailabilityMode);
+                    
+                RebindManager rebindManager = managementContext.getRebindManager();
+                
+                BrooklynMementoPersisterToObjectStore persister = new BrooklynMementoPersisterToObjectStore(
+                    objectStore,
+                    ((ManagementContextInternal)managementContext).getBrooklynProperties(),
+                    managementContext.getCatalogClassLoader());
+                PersistenceExceptionHandler persistenceExceptionHandler = PersistenceExceptionHandlerImpl.builder().build();
+                ((RebindManagerImpl) rebindManager).setPeriodicPersistPeriod(persistPeriod);
+                rebindManager.setPersister(persister, persistenceExceptionHandler);
+            } catch (FatalConfigurationRuntimeException e) {
+                throw e;
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                LOG.debug("Error initializing persistence subsystem (rethrowing): "+e, e);
+                throw new FatalRuntimeException("Error initializing persistence subsystem: "+
+                    Exceptions.collapseText(e), e);
+            }
+        }
+        
+        // Initialise the HA manager as required
+        if (highAvailabilityMode == HighAvailabilityMode.DISABLED) {
+            LOG.info("High availability disabled");
+        } else {
+            if (objectStore==null)
+                throw new FatalConfigurationRuntimeException("Cannot run in HA mode when no persistence configured.");
+
+            HighAvailabilityManager haManager = managementContext.getHighAvailabilityManager();
+            ManagementPlaneSyncRecordPersister persister =
+                new ManagementPlaneSyncRecordPersisterToObjectStore(managementContext,
+                    objectStore,
+                    managementContext.getCatalogClassLoader());
+            ((HighAvailabilityManagerImpl)haManager).setHeartbeatTimeout(haHeartbeatTimeoutOverride);
+            ((HighAvailabilityManagerImpl)haManager).setPollPeriod(haHeartbeatPeriodOverride);
+            haManager.setPersister(persister);
+        }
+    }
+    
+    protected void startPersistence() {
+        // Now start the HA Manager and the Rebind manager, as required
+        if (highAvailabilityMode == HighAvailabilityMode.DISABLED) {
+            HighAvailabilityManager haManager = managementContext.getHighAvailabilityManager();
+            haManager.disabled();
+
+            if (persistMode != PersistMode.DISABLED) {
+                startPersistenceWithoutHA();
+            }
+            
+        } else {
+            // Let the HA manager decide when objectstore.prepare and rebindmgr.rebind need to be called 
+            // (based on whether other nodes in plane are already running).
+            
+            HighAvailabilityMode startMode=null;
+            switch (highAvailabilityMode) {
+                case AUTO:
+                case MASTER:
+                case STANDBY:
+                case HOT_STANDBY:
+                case HOT_BACKUP:
+                    startMode = highAvailabilityMode;
+                    break;
+                case DISABLED:
+                    throw new IllegalStateException("Unexpected code-branch for high availability mode "+highAvailabilityMode);
+            }
+            if (startMode==null)
+                throw new IllegalStateException("Unexpected high availability mode "+highAvailabilityMode);
+            
+            LOG.debug("Management node (with HA) starting");
+            HighAvailabilityManager haManager = managementContext.getHighAvailabilityManager();
+            // prepare after HA mode is known, to prevent backups happening in standby mode
+            haManager.start(startMode);
+        }
+    }
+
+    private void startPersistenceWithoutHA() {
+        RebindManager rebindManager = managementContext.getRebindManager();
+        if (Strings.isNonBlank(persistenceLocation))
+            LOG.info("Management node (no HA) rebinding to entities at "+persistenceLocation+" in "+persistenceDir);
+        else
+            LOG.info("Management node (no HA) rebinding to entities on file system in "+persistenceDir);
+
+        ClassLoader classLoader = managementContext.getCatalogClassLoader();
+        try {
+            rebindManager.rebind(classLoader, null, ManagementNodeState.MASTER);
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            LOG.debug("Error rebinding to persisted state (rethrowing): "+e, e);
+            throw new FatalRuntimeException("Error rebinding to persisted state: "+
+                Exceptions.collapseText(e), e);
+        }
+        rebindManager.startPersistence();
+    }
+
+    protected void createApps() {
+        for (ApplicationBuilder appBuilder : appBuildersToManage) {
+            StartableApplication app = appBuilder.manage(managementContext);
+            apps.add(app);
+        }
+        for (Application app : appsToManage) {
+            Entities.startManagement(app, managementContext);
+            apps.add(app);
+        }
+        for (String blueprint : yamlAppsToManage) {
+            Application app = getAppFromYaml(blueprint);
+            // Note: BrooklynAssemblyTemplateInstantiator automatically puts applications under management.
+            apps.add(app);
+        }
+    }
+
+    protected void startBrooklynNode() {
+        final String classpath = System.getenv("INITIAL_CLASSPATH");
+        if (Strings.isBlank(classpath)) {
+            LOG.warn("Cannot find INITIAL_CLASSPATH environment variable, skipping BrooklynNode entity creation");
+            return;
+        }
+        if (webServer == null || !startWebApps) {
+            LOG.info("Skipping BrooklynNode entity creation, BrooklynWebServer not running");
+            return;
+        }
+        ApplicationBuilder brooklyn = new ApplicationBuilder() {
+            @SuppressWarnings("deprecation")
+            @Override
+            protected void doBuild() {
+                addChild(EntitySpec.create(LocalBrooklynNode.class)
+                        .configure(SoftwareProcess.ENTITY_STARTED, true)
+                        .configure(SoftwareProcess.RUN_DIR, System.getenv("ROOT"))
+                        .configure(SoftwareProcess.INSTALL_DIR, System.getenv("BROOKLYN_HOME"))
+                        .configure(BrooklynNode.ENABLED_HTTP_PROTOCOLS, ImmutableList.of(webServer.getHttpsEnabled() ? "https" : "http"))
+                        .configure(webServer.getHttpsEnabled() ? BrooklynNode.HTTPS_PORT : BrooklynNode.HTTP_PORT, PortRanges.fromInteger(webServer.getActualPort()))
+                        .configure(BrooklynNode.WEB_CONSOLE_BIND_ADDRESS, bindAddress)
+                        .configure(BrooklynNode.WEB_CONSOLE_PUBLIC_ADDRESS, publicAddress)
+                        .configure(BrooklynNode.CLASSPATH, Splitter.on(":").splitToList(classpath))
+                        .configure(BrooklynNode.NO_WEB_CONSOLE_AUTHENTICATION, Boolean.TRUE.equals(skipSecurityFilter))
+                        .displayName("Brooklyn Console"));
+            }
+        };
+        LocationSpec<?> spec = LocationSpec.create(LocalhostMachine.class).displayName("Local Brooklyn");
+        Location localhost = managementContext.getLocationManager().createLocation(spec);
+        brooklyn.appDisplayName("Brooklyn")
+                .manage(managementContext)
+                .start(ImmutableList.of(localhost));
+    }
+
+    protected Application getAppFromYaml(String input) {
+        AssemblyTemplate at = campPlatform.pdp().registerDeploymentPlan(new StringReader(input));
+        BrooklynAssemblyTemplateInstantiator instantiator;
+        try {
+            AssemblyTemplateInstantiator ati = at.getInstantiator().newInstance();
+            if (ati instanceof BrooklynAssemblyTemplateInstantiator) {
+                instantiator = BrooklynAssemblyTemplateInstantiator.class.cast(ati);
+            } else {
+                throw new IllegalStateException("Cannot create application with instantiator: " + ati);
+            }
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+        Application app = instantiator.create(at, campPlatform);
+        return app;
+    }
+    
+    protected void startApps() {
+        if ((stopWhichAppsOnShutdown==StopWhichAppsOnShutdown.ALL) || 
+            (stopWhichAppsOnShutdown==StopWhichAppsOnShutdown.ALL_IF_NOT_PERSISTED && persistMode==PersistMode.DISABLED)) {
+            BrooklynShutdownHooks.invokeStopAppsOnShutdown(managementContext);
+        }
+
+        List<Throwable> appExceptions = Lists.newArrayList();
+        for (Application app : apps) {
+            if (app instanceof Startable) {
+                
+                if ((stopWhichAppsOnShutdown==StopWhichAppsOnShutdown.THESE) || 
+                    (stopWhichAppsOnShutdown==StopWhichAppsOnShutdown.THESE_IF_NOT_PERSISTED && persistMode==PersistMode.DISABLED)) {
+                    BrooklynShutdownHooks.invokeStopOnShutdown(app);
+                }
+                try {
+                    LOG.info("Starting brooklyn application {} in location{} {}", new Object[] { app, locations.size()!=1?"s":"", locations });
+                    ((Startable)app).start(locations);
+                } catch (Exception e) {
+                    LOG.error("Error starting "+app+": "+Exceptions.collapseText(e), Exceptions.getFirstInteresting(e));
+                    appExceptions.add(Exceptions.collapse(e));
+                    
+                    if (Thread.currentThread().isInterrupted()) {
+                        LOG.error("Interrupted while starting applications; aborting");
+                        break;
+                    }
+                }
+            }
+        }
+        if (!appExceptions.isEmpty()) {
+            Throwable t = Exceptions.create(appExceptions);
+            throw new FatalRuntimeException("Error starting applications: "+Exceptions.collapseText(t), t);
+        }
+    }
+    
+    public boolean isStarted() {
+        return started;
+    }
+    
+    /**
+     * Terminates this launch, but does <em>not</em> stop the applications (i.e. external processes
+     * are left running, etc). However, by terminating the management console the brooklyn applications
+     * become unusable.
+     */
+    public void terminate() {
+        if (!started) return; // no-op
+        
+        if (webServer != null) {
+            try {
+                webServer.stop();
+            } catch (Exception e) {
+                LOG.warn("Error stopping web-server; continuing with termination", e);
+            }
+        }
+
+        // TODO Do we want to do this as part of managementContext.terminate, so after other threads are terminated etc?
+        // Otherwise the app can change between this persist and the terminate.
+        if (persistMode != PersistMode.DISABLED) {
+            try {
+                Stopwatch stopwatch = Stopwatch.createStarted();
+                if (managementContext.getHighAvailabilityManager().getPersister() != null) {
+                    managementContext.getHighAvailabilityManager().getPersister().waitForWritesCompleted(Duration.TEN_SECONDS);
+                }
+                managementContext.getRebindManager().waitForPendingComplete(Duration.TEN_SECONDS, true);
+                LOG.info("Finished waiting for persist; took "+Time.makeTimeStringRounded(stopwatch));
+            } catch (RuntimeInterruptedException e) {
+                Thread.currentThread().interrupt(); // keep going with shutdown
+                LOG.warn("Persistence interrupted during shutdown: "+e, e);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt(); // keep going with shutdown
+                LOG.warn("Persistence interrupted during shutdown: "+e, e);
+            } catch (TimeoutException e) {
+                LOG.warn("Timeout after 10 seconds waiting for persistence to write all data; continuing");
+            }
+        }
+        
+        if (managementContext instanceof ManagementContextInternal) {
+            ((ManagementContextInternal)managementContext).terminate();
+        }
+        
+        for (Location loc : locations) {
+            if (loc instanceof Closeable) {
+                Streams.closeQuietly((Closeable)loc);
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f58ef3e/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynServerDetails.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynServerDetails.java b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynServerDetails.java
new file mode 100644
index 0000000..1ffa8cf
--- /dev/null
+++ b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynServerDetails.java
@@ -0,0 +1,47 @@
+/*
+ * 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.launcher;
+
+import brooklyn.management.ManagementContext;
+
+public class BrooklynServerDetails {
+
+    protected BrooklynWebServer webServer;
+    protected ManagementContext mgmtContext;
+    
+    public BrooklynServerDetails(BrooklynWebServer webServer, ManagementContext mgmtContext) {
+        super();
+        this.webServer = webServer;
+        this.mgmtContext = mgmtContext;
+    }
+
+    public BrooklynWebServer getWebServer() {
+        return webServer;
+    }
+    
+    public String getWebServerUrl() {
+        if (webServer==null) return null;
+        return webServer.getRootUrl();
+    }
+    
+    public ManagementContext getManagementContext() {
+        return mgmtContext;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f58ef3e/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
new file mode 100644
index 0000000..1f2bc5d
--- /dev/null
+++ b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
@@ -0,0 +1,652 @@
+/*
+ * 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.launcher;
+
+import java.io.File;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.URI;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+import javax.servlet.DispatcherType;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import com.sun.jersey.api.container.filter.GZIPContentEncodingFilter;
+import com.sun.jersey.api.core.DefaultResourceConfig;
+import com.sun.jersey.api.core.ResourceConfig;
+import com.sun.jersey.spi.container.servlet.ServletContainer;
+
+import brooklyn.BrooklynVersion;
+import brooklyn.config.BrooklynServerPaths;
+import brooklyn.config.BrooklynServiceAttributes;
+import brooklyn.config.ConfigKey;
+import brooklyn.internal.BrooklynInitialization;
+import org.apache.brooklyn.launcher.config.CustomResourceLocator;
+import brooklyn.location.PortRange;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.location.basic.PortRanges;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.rest.BrooklynRestApi;
+import brooklyn.rest.BrooklynWebConfig;
+import brooklyn.rest.filter.BrooklynPropertiesSecurityFilter;
+import brooklyn.rest.filter.HaHotCheckResourceFilter;
+import brooklyn.rest.filter.HaMasterCheckFilter;
+import brooklyn.rest.filter.LoggingFilter;
+import brooklyn.rest.filter.NoCacheFilter;
+import brooklyn.rest.filter.RequestTaggingFilter;
+import brooklyn.rest.util.ManagementContextProvider;
+import brooklyn.rest.util.ShutdownHandler;
+import brooklyn.rest.util.ShutdownHandlerProvider;
+import brooklyn.util.BrooklynNetworkUtils;
+import brooklyn.util.ResourceUtils;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.crypto.FluentKeySigner;
+import brooklyn.util.crypto.SecureKeys;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.flags.FlagUtils;
+import brooklyn.util.flags.SetFromFlag;
+import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.io.FileUtil;
+import brooklyn.util.javalang.Threads;
+import brooklyn.util.logging.LoggingSetup;
+import brooklyn.util.os.Os;
+import brooklyn.util.stream.Streams;
+import brooklyn.util.text.Identifiers;
+import brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.web.ContextHandlerCollectionHotSwappable;
+
+/**
+ * Starts the web-app running, connected to the given management context
+ */
+public class BrooklynWebServer {
+    private static final Logger log = LoggerFactory.getLogger(BrooklynWebServer.class);
+
+    public static final String BROOKLYN_WAR_URL = "classpath://brooklyn.war";
+    static {
+        // support loading the WAR in dev mode from an alternate location 
+        CustomResourceLocator.registerAlternateLocator(new CustomResourceLocator.SearchingClassPathInDevMode(
+                BROOKLYN_WAR_URL, "/usage/launcher/target", 
+                "/usage/jsgui/target/brooklyn-jsgui-"+BrooklynVersion.get()+".war"));
+    }
+    
+    static {
+        LoggingSetup.installJavaUtilLoggingBridge();
+    }
+    
+    protected Server server;
+
+    private WebAppContext rootContext;
+    
+    /** base port to use, for http if enabled or else https; if not set, it uses httpPort or httpsPort */
+    @SetFromFlag("port")
+    protected PortRange requestedPort = null;
+    
+    @SetFromFlag
+    protected PortRange httpPort = PortRanges.fromString("8081+");
+    @SetFromFlag
+    protected PortRange httpsPort = PortRanges.fromString("8443+");
+    
+    /** actual port where this gets bound; will be consistent with the "port" passed in
+     * but that might be a range and here it is a single port, or -1 if not yet set */
+    protected volatile int actualPort = -1;
+    /** actual NIC where this is listening; in the case of 0.0.0.0 being passed in as bindAddress,
+     * this will revert to one address (such as localhost) */
+    protected InetAddress actualAddress = null;
+
+    @SetFromFlag
+    protected String war = BROOKLYN_WAR_URL;
+
+    /** IP of NIC where this server should bind, or null to autodetect 
+     * (e.g. 0.0.0.0 if security is configured, or loopback if no security) */
+    @SetFromFlag
+    protected InetAddress bindAddress = null;
+
+    /** The address that this server's management context will be publically available on. */
+    @SetFromFlag
+    protected InetAddress publicAddress = null;
+
+    /**
+     * map of context-prefix to file
+     */
+    @SetFromFlag
+    private Map<String, String> wars = new LinkedHashMap<String, String>();
+
+    @SetFromFlag
+    protected boolean ignoreWebappDeploymentFailures = false;
+
+    @SetFromFlag
+    private Map<String, Object> attributes = new LinkedHashMap<String, Object>();
+
+    private ManagementContext managementContext;
+
+    @SetFromFlag
+    private Boolean httpsEnabled;
+
+    @SetFromFlag
+    private String sslCertificate;
+
+    @SetFromFlag
+    private String keystoreUrl;
+
+    @SetFromFlag @Deprecated /** @deprecated use keystoreUrl */
+    private String keystorePath;
+
+    @SetFromFlag
+    private String keystorePassword;
+
+    @SetFromFlag
+    private String keystoreCertAlias;
+
+    @SetFromFlag
+    private String truststorePath;
+
+    @SetFromFlag
+    private String trustStorePassword;
+    
+    @SetFromFlag
+    private String transportProtocols;
+    
+    @SetFromFlag
+    private String transportCiphers;
+
+    private File webappTempDir;
+    
+    private Class<BrooklynPropertiesSecurityFilter> securityFilterClazz;
+
+    private ShutdownHandler shutdownHandler;
+
+    public BrooklynWebServer(ManagementContext managementContext) {
+        this(Maps.newLinkedHashMap(), managementContext);
+    }
+
+    /**
+     * accepts flags:  port,
+     * war (url of war file which is the root),
+     * wars (map of context-prefix to url),
+     * attrs (map of attribute-name : object pairs passed to the servlet)
+     */
+    public BrooklynWebServer(Map<?,?> flags, ManagementContext managementContext) {
+        this.managementContext = managementContext;
+        Map<?,?> leftovers = FlagUtils.setFieldsFromFlags(flags, this);
+        if (!leftovers.isEmpty())
+            log.warn("Ignoring unknown flags " + leftovers);
+        
+        webappTempDir = BrooklynServerPaths.getBrooklynWebTmpDir(managementContext);
+    }
+
+    public BrooklynWebServer(ManagementContext managementContext, int port) {
+        this(managementContext, port, "brooklyn.war");
+    }
+
+    public BrooklynWebServer(ManagementContext managementContext, int port, String warUrl) {
+        this(MutableMap.of("port", port, "war", warUrl), managementContext);
+    }
+
+    public void setSecurityFilter(Class<BrooklynPropertiesSecurityFilter> filterClazz) {
+        this.securityFilterClazz = filterClazz;
+    }
+
+    public void setShutdownHandler(@Nullable ShutdownHandler shutdownHandler) {
+        this.shutdownHandler = shutdownHandler;
+    }
+
+    public BrooklynWebServer setPort(Object port) {
+        if (getActualPort()>0)
+            throw new IllegalStateException("Can't set port after port has been assigned to server (using "+getActualPort()+")");
+        this.requestedPort = TypeCoercions.coerce(port, PortRange.class);
+        return this;
+    }
+
+    @VisibleForTesting
+    File getWebappTempDir() {
+        return webappTempDir;
+    }
+    
+    public BrooklynWebServer setHttpsEnabled(Boolean httpsEnabled) {
+        this.httpsEnabled = httpsEnabled;
+        return this;
+    }
+    
+    public boolean getHttpsEnabled() {
+        return getConfig(httpsEnabled, BrooklynWebConfig.HTTPS_REQUIRED);
+    }
+    
+    public PortRange getRequestedPort() {
+        return requestedPort;
+    }
+    
+    /** returns port where this is running, or -1 if not yet known */
+    public int getActualPort() {
+        return actualPort;
+    }
+
+    /** interface/address where this server is listening;
+     * if bound to 0.0.0.0 (all NICs, e.g. because security is set) this will return one NIC where this is bound */
+    public InetAddress getAddress() {
+        return actualAddress;
+    }
+    
+    /** URL for accessing this web server (root context) */
+    public String getRootUrl() {
+        String address = (publicAddress != null) ? publicAddress.getHostName() : getAddress().getHostName();
+        if (getActualPort()>0){
+            String protocol = getHttpsEnabled()?"https":"http";
+            return protocol+"://"+address+":"+getActualPort()+"/";
+        } else {
+            return null;
+        }
+    }
+
+      /** sets the WAR to use as the root context (only if server not yet started);
+     * cf deploy("/", url) */
+    public BrooklynWebServer setWar(String url) {
+        this.war = url;
+        return this;
+    }
+
+    /** specifies a WAR to use at a given context path (only if server not yet started);
+     * cf deploy(path, url) */
+    public BrooklynWebServer addWar(String path, String warUrl) {
+        wars.put(path, warUrl);
+        return this;
+    }
+
+    /** InetAddress to which server should bind;
+     * defaults to 0.0.0.0 (although common call path is to set to 127.0.0.1 when security is not set) */
+    public BrooklynWebServer setBindAddress(InetAddress address) {
+        bindAddress = address;
+        return this;
+    }
+
+    /**
+     * Sets the public address that the server's management context's REST API will be available on
+     */
+    public BrooklynWebServer setPublicAddress(InetAddress address) {
+        publicAddress = address;
+        return this;
+    }
+
+    /** @deprecated use setAttribute */
+    public BrooklynWebServer addAttribute(String field, Object value) {
+        return setAttribute(field, value);
+    }
+    /** Specifies an attribute passed to deployed webapps 
+     * (in addition to {@link BrooklynServiceAttributes#BROOKLYN_MANAGEMENT_CONTEXT} */
+    public BrooklynWebServer setAttribute(String field, Object value) {
+        attributes.put(field, value);
+        return this;
+    }
+    
+    public <T> BrooklynWebServer configure(ConfigKey<T> key, T value) {
+        return setAttribute(key.getName(), value);
+    }
+
+    /** Specifies attributes passed to deployed webapps 
+     * (in addition to {@link BrooklynServiceAttributes#BROOKLYN_MANAGEMENT_CONTEXT} */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public BrooklynWebServer putAttributes(Map newAttrs) {
+        if (newAttrs!=null) attributes.putAll(newAttrs);
+        return this;
+    }
+
+    public void installAsServletFilter(ServletContextHandler context) {
+        ResourceConfig config = new DefaultResourceConfig();
+        // load all our REST API modules, JSON, and Swagger
+        for (Object r: BrooklynRestApi.getAllResources())
+            config.getSingletons().add(r);
+
+        // Accept gzipped requests and responses, disable caching for dynamic content
+        config.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS, GZIPContentEncodingFilter.class.getName());
+        config.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS, ImmutableList.of(GZIPContentEncodingFilter.class, NoCacheFilter.class));
+        // Checks if appropriate request given HA status
+        config.getProperties().put(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, HaHotCheckResourceFilter.class.getName());
+        // configure to match empty path, or any thing which looks like a file path with /assets/ and extension html, css, js, or png
+        // and treat that as static content
+        config.getProperties().put(ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX, "(/?|[^?]*/assets/[^?]+\\.[A-Za-z0-9_]+)");
+        // and anything which is not matched as a servlet also falls through (but more expensive than a regex check?)
+        config.getFeatures().put(ServletContainer.FEATURE_FILTER_FORWARD_ON_404, true);
+        // finally create this as a _filter_ which falls through to a web app or something (optionally)
+        FilterHolder filterHolder = new FilterHolder(new ServletContainer(config));
+
+        context.addFilter(filterHolder, "/*", EnumSet.allOf(DispatcherType.class));
+
+        ManagementContext mgmt = (ManagementContext) context.getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT);
+        config.getSingletons().add(new ManagementContextProvider(mgmt));
+
+        config.getSingletons().add(new ShutdownHandlerProvider(shutdownHandler));
+    }
+
+    ContextHandlerCollectionHotSwappable handlers = new ContextHandlerCollectionHotSwappable();
+    
+    /**
+     * Starts the embedded web application server.
+     */
+    public synchronized void start() throws Exception {
+        if (server != null) throw new IllegalStateException(""+this+" already running");
+
+        if (actualPort == -1){
+            PortRange portRange = getConfig(requestedPort, BrooklynWebConfig.WEB_CONSOLE_PORT);
+            if (portRange==null) {
+                portRange = getHttpsEnabled() ? httpsPort : httpPort;
+            }
+            actualPort = LocalhostMachineProvisioningLocation.obtainPort(getAddress(), portRange);
+            if (actualPort == -1) 
+                throw new IllegalStateException("Unable to provision port for web console (wanted "+portRange+")");
+        }
+
+        server = new Server();
+        final Connector connector;
+        if (getHttpsEnabled()) {
+            connector = new SslSelectChannelConnector(createContextFactory());
+        } else {
+            connector = new SelectChannelConnector();
+        }
+        if (bindAddress != null) {
+            connector.setHost(bindAddress.getHostName());
+        }
+        connector.setPort(actualPort);
+        server.setConnectors(new Connector[]{connector});
+
+        if (bindAddress == null || bindAddress.equals(InetAddress.getByAddress(new byte[] { 0, 0, 0, 0 }))) {
+            actualAddress = BrooklynNetworkUtils.getLocalhostInetAddress();
+        } else {
+            actualAddress = bindAddress;
+        }
+
+        // use a nice name in the thread pool (otherwise this is exactly the same as Server defaults)
+        QueuedThreadPool threadPool = new QueuedThreadPool();
+        threadPool.setName("brooklyn-jetty-server-"+actualPort+"-"+threadPool.getName());
+        server.setThreadPool(threadPool);
+
+        if (log.isDebugEnabled())
+            log.debug("Starting Brooklyn console at "+getRootUrl()+", running " + war + (wars != null ? " and " + wars.values() : ""));
+        
+        addShutdownHook();
+
+        MutableMap<String, String> allWars = MutableMap.copyOf(wars);
+        String rootWar = allWars.remove("/");
+        if (rootWar==null) rootWar = war;
+        
+        for (Map.Entry<String, String> entry : allWars.entrySet()) {
+            String pathSpec = entry.getKey();
+            String warUrl = entry.getValue();
+            WebAppContext webapp = deploy(pathSpec, warUrl);
+            webapp.setTempDirectory(Os.mkdirs(new File(webappTempDir, newTimestampedDirName("war", 8))));
+        }
+        rootContext = deploy("/", rootWar);
+        rootContext.setTempDirectory(Os.mkdirs(new File(webappTempDir, "war-root")));
+
+        rootContext.addFilter(RequestTaggingFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
+        if (securityFilterClazz != null) {
+            rootContext.addFilter(securityFilterClazz, "/*", EnumSet.allOf(DispatcherType.class));
+        }
+        rootContext.addFilter(LoggingFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
+        rootContext.addFilter(HaMasterCheckFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
+        installAsServletFilter(rootContext);
+
+        server.setHandler(handlers);
+        server.start();
+        //reinit required because some webapps (eg grails) might wipe our language extension bindings
+        BrooklynInitialization.reinitAll();
+
+        if (managementContext instanceof ManagementContextInternal) {
+            ((ManagementContextInternal) managementContext).setManagementNodeUri(new URI(getRootUrl()));
+        }
+
+        log.info("Started Brooklyn console at "+getRootUrl()+", running " + rootWar + (allWars!=null && !allWars.isEmpty() ? " and " + wars.values() : ""));
+    }
+
+    private SslContextFactory createContextFactory() throws KeyStoreException {
+        SslContextFactory sslContextFactory = new SslContextFactory();
+
+        // allow webconsole keystore & related properties to be set in brooklyn.properties
+        String ksUrl = getKeystoreUrl();
+        String ksPassword = getConfig(keystorePassword, BrooklynWebConfig.KEYSTORE_PASSWORD);
+        String ksCertAlias = getConfig(keystoreCertAlias, BrooklynWebConfig.KEYSTORE_CERTIFICATE_ALIAS);
+        String trProtos = getConfig(transportProtocols, BrooklynWebConfig.TRANSPORT_PROTOCOLS);
+        String trCiphers = getConfig(transportCiphers, BrooklynWebConfig.TRANSPORT_CIPHERS);
+        
+        if (ksUrl!=null) {
+            sslContextFactory.setKeyStorePath(getLocalKeyStorePath(ksUrl));
+            if (Strings.isEmpty(ksPassword))
+                throw new IllegalArgumentException("Keystore password is required and non-empty if keystore is specified.");
+            sslContextFactory.setKeyStorePassword(ksPassword);
+            if (Strings.isNonEmpty(ksCertAlias))
+                sslContextFactory.setCertAlias(ksCertAlias);
+        } else {
+            log.info("No keystore specified but https enabled; creating a default keystore");
+            
+            if (Strings.isEmpty(ksCertAlias))
+                ksCertAlias = "web-console";
+            
+            // if password is blank the process will block and read from stdin !
+            if (Strings.isEmpty(ksPassword)) {
+                ksPassword = Identifiers.makeRandomId(8);
+                log.debug("created random password "+ksPassword+" for ad hoc internal keystore");
+            }
+            
+            KeyStore ks = SecureKeys.newKeyStore();
+            KeyPair key = SecureKeys.newKeyPair();
+            X509Certificate cert = new FluentKeySigner("brooklyn").newCertificateFor("web-console", key);
+            ks.setKeyEntry(ksCertAlias, key.getPrivate(), ksPassword.toCharArray(),
+                new Certificate[] { cert });
+            
+            sslContextFactory.setKeyStore(ks);
+            sslContextFactory.setKeyStorePassword(ksPassword);
+            sslContextFactory.setCertAlias(ksCertAlias);
+        }
+        if (!Strings.isEmpty(truststorePath)) {
+            sslContextFactory.setTrustStore(checkFileExists(truststorePath, "truststore"));
+            sslContextFactory.setTrustStorePassword(trustStorePassword);
+        }
+
+        if (Strings.isNonBlank(trProtos)) {
+            sslContextFactory.setIncludeProtocols(parseArray(trProtos));
+        }
+        if (Strings.isNonBlank(trCiphers)) {
+            sslContextFactory.setIncludeCipherSuites(parseArray(trCiphers));
+        }
+        return sslContextFactory;
+    }
+
+    private String[] parseArray(String list) {
+        List<String> arr = Splitter.on(",").omitEmptyStrings().trimResults().splitToList(list);
+        return arr.toArray(new String[arr.size()]);
+    }
+
+    private String getKeystoreUrl() {
+        if (keystoreUrl != null) {
+            if (Strings.isNonBlank(keystorePath) && !keystoreUrl.equals(keystorePath)) {
+                log.warn("Deprecated 'keystorePath' supplied with different value than 'keystoreUrl', preferring the latter: "+
+                        keystorePath+" / "+keystoreUrl);
+            }
+            return keystoreUrl;
+        } else if (Strings.isNonBlank(keystorePath)) {
+            log.warn("Deprecated 'keystorePath' used; callers should use 'keystoreUrl'");
+            return keystorePath;
+        } else {
+            return managementContext.getConfig().getConfig(BrooklynWebConfig.KEYSTORE_URL);
+        }
+    }
+
+    private <T> T getConfig(T override, ConfigKey<T> key) {
+        if (override!=null) {
+            return override;
+        } else {
+            return managementContext.getConfig().getConfig(key);
+        }
+    }
+
+    private String getLocalKeyStorePath(String keystoreUrl) {
+        ResourceUtils res = ResourceUtils.create(this);
+        res.checkUrlExists(keystoreUrl, BrooklynWebConfig.KEYSTORE_URL.getName());
+        if (new File(keystoreUrl).exists()) {
+            return keystoreUrl;
+        } else {
+            InputStream keystoreStream;
+            try {
+                keystoreStream = res.getResourceFromUrl(keystoreUrl);
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                throw new IllegalArgumentException("Unable to access URL: "+keystoreUrl, e);
+            }
+            File tmp = Os.newTempFile("brooklyn-keystore", "ks");
+            tmp.deleteOnExit();
+            FileUtil.copyTo(keystoreStream, tmp);
+            Streams.closeQuietly(keystoreStream);
+            return tmp.getAbsolutePath();
+        }
+    }
+
+    private String newTimestampedDirName(String prefix, int randomSuffixLength) {
+        return prefix + "-" + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()) + "-" + Identifiers.makeRandomId(randomSuffixLength);
+    }
+    
+    private String checkFileExists(String path, String name) {
+        if(!new File(path).exists()){
+            throw new IllegalArgumentException("Could not find "+name+": "+path);
+        }
+        return path;
+    }
+
+    /**
+     * Asks the app server to stop and waits for it to finish up.
+     */
+    public synchronized void stop() throws Exception {
+        if (server==null) return;
+        String root = getRootUrl();
+        if (shutdownHook != null) Threads.removeShutdownHook(shutdownHook);
+        if (log.isDebugEnabled())
+            log.debug("Stopping Brooklyn web console at "+root+ " (" + war + (wars != null ? " and " + wars.values() : "") + ")");
+
+        server.stop();
+        try {
+            server.join();
+        } catch (Exception e) {
+            /* NPE may be thrown e.g. if threadpool not started */
+        }
+        server = null;
+        LocalhostMachineProvisioningLocation.releasePort(getAddress(), actualPort);
+        actualPort = -1;
+        if (log.isDebugEnabled())
+            log.debug("Stopped Brooklyn web console at "+root);
+    }
+
+    /** serve given WAR at the given pathSpec; if not yet started, it is simply remembered until start;
+     * if server already running, the context for this WAR is started.
+     * @return the context created and added as a handler 
+     * (and possibly already started if server is started,
+     * so be careful with any changes you make to it!)  */
+    public WebAppContext deploy(final String pathSpec, final String warUrl) {
+        String cleanPathSpec = pathSpec;
+        while (cleanPathSpec.startsWith("/"))
+            cleanPathSpec = cleanPathSpec.substring(1);
+        boolean isRoot = cleanPathSpec.isEmpty();
+
+        WebAppContext context = new WebAppContext();
+        context.setAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT, managementContext);
+        for (Map.Entry<String, Object> attributeEntry : attributes.entrySet()) {
+            context.setAttribute(attributeEntry.getKey(), attributeEntry.getValue());
+        }
+
+        try {
+            File tmpWarFile = Os.writeToTempFile(new CustomResourceLocator(managementContext.getConfig(), ResourceUtils.create(this)).getResourceFromUrl(warUrl), 
+                    isRoot ? "ROOT" : ("embedded-" + cleanPathSpec), ".war");
+            context.setWar(tmpWarFile.getAbsolutePath());
+        } catch (Exception e) {
+            log.warn("Failed to deploy webapp "+pathSpec+" from "+warUrl
+                + (ignoreWebappDeploymentFailures ? "; launching run without WAR" : " (rethrowing)")
+                + ": "+Exceptions.collapseText(e));
+            if (!ignoreWebappDeploymentFailures) {
+                throw new IllegalStateException("Failed to deploy webapp "+pathSpec+" from "+warUrl+": "+Exceptions.collapseText(e), e);
+            }
+            log.debug("Detail on failure to deploy webapp: "+e, e);
+            context.setWar("/dev/null");
+        }
+
+        context.setContextPath("/" + cleanPathSpec);
+        context.setParentLoaderPriority(true);
+
+        deploy(context);
+        return context;
+    }
+
+    private Thread shutdownHook = null;
+
+    protected synchronized void addShutdownHook() {
+        if (shutdownHook!=null) return;
+        // some webapps can generate a lot of output if we don't shut down the browser first
+        shutdownHook = Threads.addShutdownHook(new Runnable() {
+            @Override
+            public void run() {
+                log.debug("BrooklynWebServer detected shutdown: stopping web-console");
+                try {
+                    stop();
+                } catch (Exception e) {
+                    log.error("Failure shutting down web-console: "+e, e);
+                }
+            }
+        });
+    }
+
+    public void deploy(WebAppContext context) {
+        try {
+            handlers.updateHandler(context);
+        } catch (Exception e) {
+            Throwables.propagate(e);
+        }
+    }
+    
+    public Server getServer() {
+        return server;
+    }
+    
+    public WebAppContext getRootContext() {
+        return rootContext;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f58ef3e/usage/launcher/src/main/java/org/apache/brooklyn/launcher/camp/BrooklynCampPlatformLauncher.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/org/apache/brooklyn/launcher/camp/BrooklynCampPlatformLauncher.java b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/camp/BrooklynCampPlatformLauncher.java
new file mode 100644
index 0000000..72732cd
--- /dev/null
+++ b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/camp/BrooklynCampPlatformLauncher.java
@@ -0,0 +1,72 @@
+/*
+ * 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.launcher.camp;
+
+import io.brooklyn.camp.CampServer;
+import io.brooklyn.camp.spi.PlatformRootSummary;
+import brooklyn.entity.basic.BrooklynShutdownHooks;
+import org.apache.brooklyn.launcher.BrooklynLauncher;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.internal.LocalManagementContext;
+
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatform;
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherAbstract;
+
+import com.google.common.annotations.Beta;
+
+/** variant of super who also starts a CampServer for convenience */
+@Beta
+public class BrooklynCampPlatformLauncher extends BrooklynCampPlatformLauncherAbstract {
+
+    protected BrooklynLauncher brooklynLauncher;
+    protected CampServer campServer;
+
+    @Override
+    public BrooklynCampPlatformLauncher launch() {
+        assert platform == null;
+
+        mgmt = newManagementContext();
+        
+        // We created the management context, so we are responsible for terminating it
+        BrooklynShutdownHooks.invokeTerminateOnShutdown(mgmt);
+
+        brooklynLauncher = BrooklynLauncher.newInstance().managementContext(mgmt).start();
+        platform = new BrooklynCampPlatform(
+                PlatformRootSummary.builder().name("Brooklyn CAMP Platform").build(),
+                mgmt).setConfigKeyAtManagmentContext();
+        
+        campServer = new CampServer(getCampPlatform(), "").start();
+        
+        return this;
+    }
+    
+    protected ManagementContext newManagementContext() {
+        return new LocalManagementContext();
+    }
+
+    public static void main(String[] args) {
+        new BrooklynCampPlatformLauncher().launch();
+    }
+
+    public void stopServers() throws Exception {
+        brooklynLauncher.getServerDetails().getWebServer().stop();
+        campServer.stop();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f58ef3e/usage/launcher/src/main/java/org/apache/brooklyn/launcher/camp/SimpleYamlLauncher.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/org/apache/brooklyn/launcher/camp/SimpleYamlLauncher.java b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/camp/SimpleYamlLauncher.java
new file mode 100644
index 0000000..200490a
--- /dev/null
+++ b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/camp/SimpleYamlLauncher.java
@@ -0,0 +1,35 @@
+/*
+ * 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.launcher.camp;
+
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherAbstract;
+import org.apache.brooklyn.camp.brooklyn.YamlLauncherAbstract;
+
+import com.google.common.annotations.Beta;
+
+/** convenience for launching YAML files directly */
+@Beta
+public class SimpleYamlLauncher extends YamlLauncherAbstract {
+
+    @Override
+    protected BrooklynCampPlatformLauncherAbstract newPlatformLauncher() {
+        return new BrooklynCampPlatformLauncher();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f58ef3e/usage/launcher/src/main/java/org/apache/brooklyn/launcher/config/BrooklynDevelopmentModes.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/org/apache/brooklyn/launcher/config/BrooklynDevelopmentModes.java b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/config/BrooklynDevelopmentModes.java
new file mode 100644
index 0000000..13d2a3b
--- /dev/null
+++ b/usage/launcher/src/main/java/org/apache/brooklyn/launcher/config/BrooklynDevelopmentModes.java
@@ -0,0 +1,93 @@
+/*
+ * 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.launcher.config;
+
+import java.io.File;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.util.os.Os;
+
+@Deprecated /** @deprecated since 0.7.0; see BrooklynVersion;
+* and anyway this was not really used, and if it were, it would be needed in core; autodetection is pretty good */
+public class BrooklynDevelopmentModes {
+
+    private static final Logger log = LoggerFactory.getLogger(BrooklynDevelopmentModes.class);
+    
+    public static final ConfigKey<BrooklynDevelopmentMode> BROOKLYN_DEV_MODE = new BasicConfigKey<BrooklynDevelopmentMode>(
+            BrooklynDevelopmentMode.class, "brooklyn.developmentMode", "whether to run in development mode " +
+                    "(default is to autodetect based on classpath)", BrooklynDevelopmentMode.AUTO);
+
+    private static AtomicBoolean loggedMode = new AtomicBoolean(false); 
+    
+    public static enum BrooklynDevelopmentMode {
+        TRUE(true), FALSE(false), AUTO(null);
+
+        private final Boolean enabled;
+        
+        BrooklynDevelopmentMode(Boolean enabled) {
+            this.enabled = enabled;
+        }
+        
+        public boolean isEnabled() {
+            boolean enabled = computeEnabled();
+            if (!loggedMode.getAndSet(true)) {
+                // log on first invocation
+                String reason = (this.enabled==null ? "autodetected" : "forced");
+                if (enabled) {
+                    log.info("Brooklyn running in development mode ("+reason+")");
+                } else {
+                    log.debug("Brooklyn not running in development mode ("+reason+")");
+                }
+            }
+            return enabled;
+        }
+        
+        protected boolean computeEnabled() {
+            if (enabled!=null) return enabled;
+            return getAutodectectedDevelopmentMode();
+        }
+    }
+    
+    private static Boolean developmentMode = null;
+    
+    public static boolean getAutodectectedDevelopmentMode() {
+        if (developmentMode!=null) return developmentMode;
+        developmentMode = computeAutodectectedDevelopmentMode();
+        return developmentMode;
+    }
+    
+    private static final String segment = "/core/target/classes";
+    
+    private static boolean computeAutodectectedDevelopmentMode() {
+        String cp = System.getProperty("java.class.path");
+        String platformSegment = Os.nativePath(segment);
+        if (cp==null) return false;
+        if (cp.endsWith(platformSegment) || cp.contains(platformSegment+File.pathSeparator)) {
+            log.debug("Brooklyn developmentMode autodetected (based on presence of '"+segment+"' in classpath)");
+            return true;
+        }
+        return false;
+    }
+    
+}