You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2015/05/22 11:04:47 UTC

[02/23] incubator-brooklyn git commit: introduce CatalogInitialization to cleanly init catalog at the right times

introduce CatalogInitialization to cleanly init catalog at the right times

invoked during persistence cycles, and at startup, holding the new CLI catalog options.
this does not yet properly initialize things for real-world use, and tests fixed in next commit.


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/0b9bc3b3
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/0b9bc3b3
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/0b9bc3b3

Branch: refs/heads/master
Commit: 0b9bc3b3966543f09eb7fb4c7be7df5abd4e6d4d
Parents: addf30e
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Apr 27 14:38:05 2015 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri May 8 18:22:21 2015 +0100

----------------------------------------------------------------------
 .../java/brooklyn/catalog/CatalogLoadMode.java  |   3 +
 .../brooklyn/catalog/CatalogPredicates.java     |   1 +
 .../catalog/internal/BasicBrooklynCatalog.java  |  38 +-
 .../brooklyn/catalog/internal/CatalogDto.java   |   9 +
 .../catalog/internal/CatalogInitialization.java | 346 +++++++++++++++++++
 .../brooklyn/config/BrooklynServerConfig.java   |  14 +-
 .../brooklyn/entity/rebind/RebindIteration.java |  74 ++--
 .../internal/AbstractManagementContext.java     |  57 ++-
 .../internal/ManagementContextInternal.java     |  19 +-
 .../NonDeploymentManagementContext.java         |  28 ++
 .../main/resources/brooklyn/empty.catalog.bom   |  18 +
 .../catalog/internal/CatalogScanTest.java       |  11 +-
 .../entity/rebind/RebindCatalogEntityTest.java  |  33 +-
 .../entity/rebind/RebindCatalogItemTest.java    |   4 +-
 .../brooklyn/entity/rebind/RebindTestUtils.java |   2 +-
 .../entity/LocalManagementContextForTests.java  |   3 +-
 usage/cli/src/main/java/brooklyn/cli/Main.java  |  29 +-
 .../brooklyn/launcher/BrooklynLauncher.java     |  41 +--
 .../brooklyn/launcher/BrooklynLauncherTest.java |   9 +-
 .../brooklyn/rest/BrooklynRestApiLauncher.java  |   3 +-
 .../BrooklynRestApiLauncherTestFixture.java     |   3 +-
 21 files changed, 594 insertions(+), 151 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/core/src/main/java/brooklyn/catalog/CatalogLoadMode.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/CatalogLoadMode.java b/core/src/main/java/brooklyn/catalog/CatalogLoadMode.java
index 259f545..6341a10 100644
--- a/core/src/main/java/brooklyn/catalog/CatalogLoadMode.java
+++ b/core/src/main/java/brooklyn/catalog/CatalogLoadMode.java
@@ -20,8 +20,11 @@ package brooklyn.catalog;
 
 import org.slf4j.LoggerFactory;
 
+import brooklyn.catalog.internal.CatalogInitialization;
 import brooklyn.entity.rebind.persister.PersistMode;
 
+/** @deprecated since 0.7.0 replaced by {@link CatalogInitialization} */
+@Deprecated
 public enum CatalogLoadMode {
     /**
      * The server will load its initial catalog from the URL configured in

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/core/src/main/java/brooklyn/catalog/CatalogPredicates.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/CatalogPredicates.java b/core/src/main/java/brooklyn/catalog/CatalogPredicates.java
index 2ffebdf..756ec80 100644
--- a/core/src/main/java/brooklyn/catalog/CatalogPredicates.java
+++ b/core/src/main/java/brooklyn/catalog/CatalogPredicates.java
@@ -72,6 +72,7 @@ public class CatalogPredicates {
         }
     };
 
+    /** @deprecated since 0.7.0 use {@link #displayName(Predicate)} */
     @Deprecated
     public static <T,SpecT> Predicate<CatalogItem<T,SpecT>> name(final Predicate<? super String> filter) {
         return displayName(filter);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
index b49a240..9d68465 100644
--- a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
+++ b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
@@ -25,7 +25,6 @@ import io.brooklyn.camp.spi.AssemblyTemplate;
 import io.brooklyn.camp.spi.instantiate.AssemblyTemplateInstantiator;
 import io.brooklyn.camp.spi.pdp.DeploymentPlan;
 
-import java.io.FileNotFoundException;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -75,7 +74,6 @@ import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
-import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSortedSet;
@@ -140,6 +138,7 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         catalog.load(mgmt, null);
         CatalogUtils.logDebugOrTraceIfRebinding(log, "Reloaded catalog for "+this+", now switching");
         this.catalog = catalog;
+        this.manualAdditionsCatalog = null;
 
         // Inject management context into and persist all the new entries.
         for (CatalogItem<?, ?> entry : getCatalogItems()) {
@@ -1102,41 +1101,6 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
             serializer = new CatalogXmlSerializer();
     }
 
-    public void resetCatalogToContentsAtConfiguredUrl() {
-        CatalogDto dto = null;
-        String catalogUrl = mgmt.getConfig().getConfig(BrooklynServerConfig.BROOKLYN_CATALOG_URL);
-        try {
-            if (!Strings.isEmpty(catalogUrl)) {
-                dto = CatalogDto.newDtoFromUrl(catalogUrl);
-                if (log.isDebugEnabled()) {
-                    log.debug("Loading catalog from {}: {}", catalogUrl, catalog);
-                }
-            }
-        } catch (Exception e) {
-            if (Throwables.getRootCause(e) instanceof FileNotFoundException) {
-                Maybe<Object> nonDefaultUrl = mgmt.getConfig().getConfigRaw(BrooklynServerConfig.BROOKLYN_CATALOG_URL, true);
-                if (nonDefaultUrl.isPresentAndNonNull() && !"".equals(nonDefaultUrl.get())) {
-                    log.warn("Could not find catalog XML specified at {}; using default (local classpath) catalog. Error was: {}", nonDefaultUrl, e);
-                } else {
-                    if (log.isDebugEnabled()) {
-                        log.debug("No default catalog file available at {}; trying again using local classpath to populate catalog. Error was: {}", catalogUrl, e);
-                    }
-                }
-            } else {
-                log.warn("Error importing catalog XML at " + catalogUrl + "; using default (local classpath) catalog. Error was: " + e, e);
-            }
-        }
-        if (dto == null) {
-            // retry, either an error, or was blank
-            dto = CatalogDto.newDefaultLocalScanningDto(CatalogClasspathDo.CatalogScanningModes.ANNOTATIONS);
-            if (log.isDebugEnabled()) {
-                log.debug("Loaded default (local classpath) catalog: " + catalog);
-            }
-        }
-
-        reset(dto);
-    }
-
     @Deprecated
     public CatalogItem<?,?> getCatalogItemForType(String typeName) {
         final CatalogItem<?,?> resultI;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/core/src/main/java/brooklyn/catalog/internal/CatalogDto.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogDto.java b/core/src/main/java/brooklyn/catalog/internal/CatalogDto.java
index 0d98c82..407cb7c 100644
--- a/core/src/main/java/brooklyn/catalog/internal/CatalogDto.java
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogDto.java
@@ -65,7 +65,15 @@ public class CatalogDto {
         return result.dto;
     }
 
+    /** @deprecated since 0.7.0 use {@link #newDtoFromXmlUrl(String)} if you must, but note the xml format itself is deprecated */
+    @Deprecated
     public static CatalogDto newDtoFromUrl(String url) {
+        return newDtoFromXmlUrl(url);
+    }
+    
+    /** @deprecated since 0.7.0 the xml format is deprecated; use YAML parse routines on BasicBrooklynCatalog */
+    @Deprecated
+    public static CatalogDto newDtoFromXmlUrl(String url) {
         if (LOG.isDebugEnabled()) LOG.debug("Retrieving catalog from: {}", url);
         try {
             InputStream source = ResourceUtils.create().getResourceFromUrl(url);
@@ -77,6 +85,7 @@ public class CatalogDto {
         }
     }
 
+    /** @deprecated since 0.7.0 the xml format is deprecated; use YAML parse routines on BasicBrooklynCatalog */
     public static CatalogDto newDtoFromXmlContents(String xmlContents, String originDescription) {
         CatalogDto result = (CatalogDto) new CatalogXmlSerializer().deserialize(new StringReader(xmlContents));
         result.contentsDescription = originDescription;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/core/src/main/java/brooklyn/catalog/internal/CatalogInitialization.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogInitialization.java b/core/src/main/java/brooklyn/catalog/internal/CatalogInitialization.java
new file mode 100644
index 0000000..db7343a
--- /dev/null
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogInitialization.java
@@ -0,0 +1,346 @@
+/*
+ * 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 brooklyn.catalog.internal;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.catalog.BrooklynCatalog;
+import brooklyn.catalog.CatalogItem;
+import brooklyn.config.BrooklynServerConfig;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.util.ResourceUtils;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.net.Urls;
+import brooklyn.util.text.Strings;
+import brooklyn.util.yaml.Yamls;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+
+@Beta
+public class CatalogInitialization {
+
+    /*
+
+    A1) if not persisting, go to B1
+    A2) if --catalog-reset, delete persisted catalog items
+    A3) read persisted catalog items (possibly deleted in A2), go to C1
+    A4) go to B1
+
+    B1) look for --catalog-initial, if so read it, then go to C1
+    B2) look for BrooklynServerConfig.BROOKLYN_CATALOG_URL, if so, read it, supporting YAML or XML (warning if XML), then go to C1
+    B3) look for ~/.brooklyn/catalog.bom, if exists, read it then go to C1
+    B4) look for ~/.brooklyn/brooklyn.xml, if exists, warn, read it then go to C1
+    B5) read all classpath://brooklyn/default.catalog.bom items, if they exist (and for now they will)
+    B6) go to C1
+
+    C1) if --catalog-add, read and add those items
+
+    D1) if persisting, read the rest of the persisted items (entities etc)
+
+     */
+
+    private static final Logger log = LoggerFactory.getLogger(CatalogInitialization.class);
+    
+    String initialUri;
+    boolean reset;
+    String additionsUri;
+    boolean force;
+
+    boolean disallowLocal = false;
+    List<Function<ManagementContext, Void>> callbacks = MutableList.of();
+    AtomicInteger runCount = new AtomicInteger();
+    
+    public CatalogInitialization(String initialUri, boolean reset, String additionUri, boolean force) {
+        this.initialUri = initialUri;
+        this.reset = reset;
+        this.additionsUri = additionUri;
+        this.force = force;
+    }
+    
+    public CatalogInitialization() {
+        this(null, false, null, false);
+    }
+
+    public CatalogInitialization addPopulationCallback(Function<ManagementContext, Void> callback) {
+        callbacks.add(callback);
+        return this;
+    }
+
+    public boolean isInitialResetRequested() {
+        return reset;
+    }
+
+    public int getRunCount() {
+        return runCount.get();
+    }
+    
+    public boolean hasRun() {
+        return getRunCount()>0;
+    }
+
+    /** makes or updates the mgmt catalog, based on the settings in this class */
+    public void populateCatalog(ManagementContext managementContext, boolean needsInitial, Collection<CatalogItem<?, ?>> optionalItemsForResettingCatalog) {
+        try {
+            BasicBrooklynCatalog catalog;
+            Maybe<BrooklynCatalog> cm = ((ManagementContextInternal)managementContext).getCatalogIfSet();
+            if (cm.isAbsent()) {
+                if (hasRun()) {
+                    log.warn("Odd: catalog initialization has run but management context has no catalog; re-creating");
+                }
+                catalog = new BasicBrooklynCatalog(managementContext);
+                setCatalog(managementContext, catalog, "Replacing catalog with newly populated catalog", true);
+            } else {
+                if (!hasRun()) {
+                    log.warn("Odd: catalog initialization has not run but management context has a catalog; re-populating");
+                }
+                catalog = (BasicBrooklynCatalog) cm.get();
+            }
+
+            populateCatalog(managementContext, catalog, needsInitial, true, optionalItemsForResettingCatalog);
+            
+        } finally {
+            runCount.incrementAndGet();
+        }
+    }
+
+    private void populateCatalog(ManagementContext managementContext, BasicBrooklynCatalog catalog, boolean needsInitial, boolean runCallbacks, Collection<CatalogItem<?, ?>> optionalItemsForResettingCatalog) {
+        applyCatalogLoadMode(managementContext);
+        
+        if (optionalItemsForResettingCatalog!=null) {
+            catalog.reset(optionalItemsForResettingCatalog);
+        }
+        
+        if (needsInitial) {
+            populateInitial(catalog, managementContext);
+        }
+        
+        populateAdditions(catalog, managementContext);
+
+        if (runCallbacks) {
+            populateViaCallbacks(catalog, managementContext);
+        }
+    }
+
+    private enum PopulateMode { YAML, XML, AUTODETECT }
+    
+    protected void populateInitial(BasicBrooklynCatalog catalog, ManagementContext managementContext) {
+        if (disallowLocal) {
+            if (!hasRun()) {
+                log.debug("CLI initial catalog not being read with disallow-local mode set.");
+            }
+            return;
+        }
+
+//        B1) look for --catalog-initial, if so read it, then go to C1
+//        B2) look for BrooklynServerConfig.BROOKLYN_CATALOG_URL, if so, read it, supporting YAML or XML (warning if XML), then go to C1
+//        B3) look for ~/.brooklyn/catalog.bom, if exists, read it then go to C1
+//        B4) look for ~/.brooklyn/brooklyn.xml, if exists, warn, read it then go to C1
+//        B5) read all classpath://brooklyn/default.catalog.bom items, if they exist (and for now they will)
+//        B6) go to C1
+
+        if (initialUri!=null) {
+            populateInitialFromUri(catalog, managementContext, initialUri, PopulateMode.AUTODETECT);
+            return;
+        }
+        
+        String catalogUrl = managementContext.getConfig().getConfig(BrooklynServerConfig.BROOKLYN_CATALOG_URL);
+        if (Strings.isNonBlank(catalogUrl)) {
+            populateInitialFromUri(catalog, managementContext, catalogUrl, PopulateMode.AUTODETECT);
+            return;
+        }
+        
+        catalogUrl = Urls.mergePaths(BrooklynServerConfig.getMgmtBaseDir( managementContext.getConfig() ), "catalog.bom");
+        if (new File(catalogUrl).exists()) {
+            populateInitialFromUri(catalog, managementContext, "file:"+catalogUrl, PopulateMode.YAML);
+            return;
+        }
+        
+        catalogUrl = Urls.mergePaths(BrooklynServerConfig.getMgmtBaseDir( managementContext.getConfig() ), "catalog.xml");
+        if (new File(catalogUrl).exists()) {
+            populateInitialFromUri(catalog, managementContext, "file:"+catalogUrl, PopulateMode.XML);
+            return;
+        }
+
+        // TODO scan for default.catalog.bom files and add all of them
+        
+//        // TODO optionally scan for classpath items
+//        // retry, either an error, or was blank
+//        dto = CatalogDto.newDefaultLocalScanningDto(CatalogClasspathDo.CatalogScanningModes.ANNOTATIONS);
+//        if (log.isDebugEnabled()) {
+//            log.debug("Loaded default (local classpath) catalog: " + catalogDo);
+//        }
+        
+        return;
+    }
+    
+    private void populateInitialFromUri(BasicBrooklynCatalog catalog, ManagementContext managementContext, String catalogUrl, PopulateMode mode) {
+        log.debug("Loading initial catalog from {}", catalogUrl);
+
+        Exception problem = null;
+        Object result = null;
+        
+        String contents = null;
+        try {
+            contents = new ResourceUtils(this).getResourceAsString(catalogUrl);
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            if (problem==null) problem = e;
+        }
+
+        if (contents!=null && (mode==PopulateMode.YAML || mode==PopulateMode.AUTODETECT)) {
+            // try YAML first
+            try {
+                catalog.reset(MutableList.<CatalogItem<?,?>>of());
+                result = catalog.addItems(contents);
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                if (problem==null) problem = e;
+            }
+        }
+        
+        if (result==null && contents!=null && (mode==PopulateMode.XML || mode==PopulateMode.AUTODETECT)) {
+            // then try XML
+            CatalogDto dto = null;
+            try {
+                dto = CatalogDto.newDtoFromXmlContents(contents, catalogUrl);
+                problem = null;
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                if (problem==null) problem = e;
+            }
+            if (dto!=null) {
+                catalog.reset(dto);
+            }
+        }
+        
+        if (result!=null) {
+            log.debug("Loaded initial catalog from {}: {}", catalogUrl, result);
+        }
+        if (problem!=null) {
+            log.warn("Error importing catalog from " + catalogUrl + ": " + problem, problem);
+            // TODO inform mgmt of error
+        }
+
+    }
+
+    protected void populateAdditions(BasicBrooklynCatalog catalog, ManagementContext mgmt) {
+        if (Strings.isNonBlank(additionsUri)) {
+            if (disallowLocal) {
+                if (!hasRun()) {
+                    log.warn("CLI additions supplied but not supported in disallow-local mode; ignoring.");
+                }
+                return;
+            }   
+            if (!hasRun()) {
+                log.debug("Adding to catalog from CLI: "+additionsUri+" (force: "+force+")");
+            }
+            Iterable<? extends CatalogItem<?, ?>> items = catalog.addItems(
+                new ResourceUtils(this).getResourceAsString(additionsUri), force);
+            
+            if (!hasRun())
+                log.debug("Added to catalog from CLI: "+items);
+            else
+                log.debug("Added to catalog from CLI: count "+Iterables.size(items));
+        }
+    }
+
+    protected void populateViaCallbacks(BasicBrooklynCatalog catalog, ManagementContext managementContext) {
+        for (Function<ManagementContext, Void> callback: callbacks)
+            callback.apply(managementContext);
+    }
+
+    private boolean setFromCatalogLoadMode = false;
+    /** @deprecated since introduced in 0.7.0, only for legacy compatibility with 
+     * {@link CatalogLoadMode} {@link BrooklynServerConfig#CATALOG_LOAD_MODE},
+     * allowing control of catalog loading from a brooklyn property */
+    @Deprecated
+    public void applyCatalogLoadMode(ManagementContext managementContext) {
+        if (setFromCatalogLoadMode) return;
+        setFromCatalogLoadMode = true;
+        Maybe<Object> clmm = ((ManagementContextInternal)managementContext).getConfig().getConfigRaw(BrooklynServerConfig.CATALOG_LOAD_MODE, false);
+        if (clmm.isAbsent()) return;
+        brooklyn.catalog.CatalogLoadMode clm = TypeCoercions.coerce(clmm.get(), brooklyn.catalog.CatalogLoadMode.class);
+        log.warn("Legacy CatalogLoadMode "+clm+" set: applying, but this should be changed to use new CLI --catalogXxx commands");
+        switch (clm) {
+        case LOAD_BROOKLYN_CATALOG_URL:
+            reset = true;
+            break;
+        case LOAD_BROOKLYN_CATALOG_URL_IF_NO_PERSISTED_STATE:
+            // now the default
+            break;
+        case LOAD_PERSISTED_STATE:
+            disallowLocal = true;
+            break;
+        }
+    }
+
+    /** makes the catalog, warning if persistence is on and hasn't run yet 
+     * (as the catalog will be subsequently replaced) */
+    @Beta
+    public BrooklynCatalog getCatalogPopulatingBestEffort(ManagementContext managementContext) {
+        Maybe<BrooklynCatalog> cm = ((ManagementContextInternal)managementContext).getCatalogIfSet();
+        if (cm.isPresent()) return cm.get();
+
+        BrooklynCatalog oldC = setCatalog(managementContext, new BasicBrooklynCatalog(managementContext),
+            "Request to make local catalog early, but someone else has created it, reverting to that", false);
+        if (oldC==null) {
+            // our catalog was added, so run population
+            // NB: we need the catalog to be saved already so that we can run callbacks
+            populateCatalog(managementContext, (BasicBrooklynCatalog) managementContext.getCatalog(), true, true, null);
+        }
+        
+        return managementContext.getCatalog();
+    }
+
+    /** Sets the catalog in the given management context, warning and choosing appropriately if one already exists. 
+     * Returns any previously existing catalog (whether or not changed). */
+    @Beta
+    public static BrooklynCatalog setCatalog(ManagementContext managementContext, BrooklynCatalog catalog, String messageIfAlready, boolean preferNew) {
+        Maybe<BrooklynCatalog> cm;
+        synchronized (managementContext) {
+            cm = ((ManagementContextInternal)managementContext).getCatalogIfSet();
+            if (cm.isAbsent()) {
+                ((ManagementContextInternal)managementContext).setCatalog(catalog);
+                return null;
+            }
+            if (preferNew) {
+                // set to null first to prevent errors
+                ((ManagementContextInternal)managementContext).setCatalog(null);
+                ((ManagementContextInternal)managementContext).setCatalog(catalog);
+            }
+        }
+        if (Strings.isNonBlank(messageIfAlready)) {
+            log.warn(messageIfAlready);
+        }
+        return cm.get();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/config/BrooklynServerConfig.java b/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
index c5ff294..a1fdd32 100644
--- a/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
+++ b/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
@@ -28,7 +28,7 @@ import java.util.Map;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import brooklyn.catalog.CatalogLoadMode;
+import brooklyn.catalog.internal.CatalogInitialization;
 import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.management.ManagementContext;
 import brooklyn.util.guava.Maybe;
@@ -98,14 +98,14 @@ public class BrooklynServerConfig {
             + "this key is DEPRECATED in favor of promotion and demotion specific flags now defaulting to true");
 
     public static final ConfigKey<String> BROOKLYN_CATALOG_URL = ConfigKeys.newStringConfigKey("brooklyn.catalog.url",
-        "The URL of a catalog.xml descriptor; absent for default (~/.brooklyn/catalog.xml), " +
-        "or empty for no URL (use default scanner)",
-        new File(Os.fromHome(".brooklyn/catalog.xml")).toURI().toString());
+        "The URL of a custom catalog.bom or catalog.xml descriptor to load");
 
-    public static final ConfigKey<CatalogLoadMode> CATALOG_LOAD_MODE = ConfigKeys.newConfigKey(CatalogLoadMode.class,
+    /** @deprecated since 0.7.0 replaced by {@link CatalogInitialization}; also note, default removed 
+     * (it was overridden anyway, and in almost all cases the new behaviour is still the default behaviour) */
+    @Deprecated
+    public static final ConfigKey<brooklyn.catalog.CatalogLoadMode> CATALOG_LOAD_MODE = ConfigKeys.newConfigKey(brooklyn.catalog.CatalogLoadMode.class,
             "brooklyn.catalog.mode",
-            "The mode the management context should use to load the catalog when first starting",
-            CatalogLoadMode.LOAD_BROOKLYN_CATALOG_URL);
+            "The mode the management context should use to load the catalog when first starting");
 
     /** string used in places where the management node ID is needed to resolve a path */
     public static final String MANAGEMENT_NODE_ID_PROPERTY = "brooklyn.mgmt.node.id";

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/core/src/main/java/brooklyn/entity/rebind/RebindIteration.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindIteration.java b/core/src/main/java/brooklyn/entity/rebind/RebindIteration.java
index 75f36f8..b5af10c 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindIteration.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindIteration.java
@@ -38,12 +38,10 @@ import brooklyn.basic.BrooklynObject;
 import brooklyn.basic.BrooklynObjectInternal;
 import brooklyn.catalog.BrooklynCatalog;
 import brooklyn.catalog.CatalogItem;
-import brooklyn.catalog.CatalogLoadMode;
-import brooklyn.catalog.internal.BasicBrooklynCatalog;
+import brooklyn.catalog.internal.CatalogInitialization;
 import brooklyn.catalog.internal.CatalogUtils;
 import brooklyn.config.BrooklynLogging;
 import brooklyn.config.BrooklynLogging.LoggingLevel;
-import brooklyn.config.BrooklynServerConfig;
 import brooklyn.enricher.basic.AbstractEnricher;
 import brooklyn.entity.Application;
 import brooklyn.entity.Entity;
@@ -300,6 +298,7 @@ public abstract class RebindIteration {
         isEmpty = mementoManifest.isEmpty();
     }
 
+    @SuppressWarnings("deprecation")
     protected void rebuildCatalog() {
         
         // build catalog early so we can load other things
@@ -341,38 +340,57 @@ public abstract class RebindIteration {
                 }
             }
         }
+
+        // see notes in CatalogInitialization
         
-        // Register catalogue items with the management context. Loads the bundles in the OSGi framework.
-        CatalogLoadMode catalogLoadMode = managementContext.getConfig().getConfig(BrooklynServerConfig.CATALOG_LOAD_MODE);
+        Collection<CatalogItem<?, ?>> catalogItems = rebindContext.getCatalogItems();
+        CatalogInitialization catInit = ((ManagementContextInternal)managementContext).getCatalogInitialization();
+        catInit.applyCatalogLoadMode(managementContext);
+        Collection<CatalogItem<?,?>> itemsForResettingCatalog = null;
+        boolean needsInitialCatalog;
         if (rebindManager.persistCatalogItemsEnabled) {
-            boolean shouldResetCatalog = catalogLoadMode == CatalogLoadMode.LOAD_PERSISTED_STATE
-                    || (!isEmpty && catalogLoadMode == CatalogLoadMode.LOAD_BROOKLYN_CATALOG_URL_IF_NO_PERSISTED_STATE);
-            boolean shouldLoadDefaultCatalog = catalogLoadMode == CatalogLoadMode.LOAD_BROOKLYN_CATALOG_URL
-                    || (isEmpty && catalogLoadMode == CatalogLoadMode.LOAD_BROOKLYN_CATALOG_URL_IF_NO_PERSISTED_STATE);
-            if (shouldResetCatalog) {
-                // Reset catalog with previously persisted state
-                logRebindingDebug("RebindManager resetting management context catalog to previously persisted state");
-                managementContext.getCatalog().reset(rebindContext.getCatalogItems());
-            } else if (shouldLoadDefaultCatalog) {
-                // Load catalogue as normal
-                // TODO in read-only mode, should do this less frequently than entities etc
-                logRebindingDebug("RebindManager loading default catalog");
-                ((BasicBrooklynCatalog) managementContext.getCatalog()).resetCatalogToContentsAtConfiguredUrl();
+            if (!catInit.hasRun() && catInit.isInitialResetRequested()) {
+                String message = "RebindManager resetting catalog on first run (catalog persistence enabled, but reset explicitly specified). ";
+                if (catalogItems.isEmpty()) {
+                    message += "Catalog was empty anyway.";
+                } else {
+                    message += "Deleting "+catalogItems.size()+" persisted catalog item"+Strings.s(catalogItems)+": "+catalogItems;
+                    if (shouldLogRebinding()) {
+                        LOG.info(message);
+                    }
+                }
+                logRebindingDebug(message);
+
+                itemsForResettingCatalog = MutableList.<CatalogItem<?,?>>of();
+                // TODO destroy persisted items
+                needsInitialCatalog = true;
             } else {
-                // Management context should have taken care of loading the catalogue
-                Collection<CatalogItem<?, ?>> catalogItems = rebindContext.getCatalogItems();
-                String message = "RebindManager not resetting catalog to persisted state. Catalog load mode is {}.";
-                if (!catalogItems.isEmpty() && shouldLogRebinding()) {
-                    LOG.info(message + " There {} {} item{} persisted.", new Object[]{
-                            catalogLoadMode, catalogItems.size() == 1 ? "was" : "were", catalogItems.size(), Strings.s(catalogItems)});
-                } else if (LOG.isDebugEnabled()) {
-                    logRebindingDebug(message, catalogLoadMode);
+                if (!isEmpty) {
+                    logRebindingDebug("RebindManager clearing local catalog and loading from persisted state");
+                    itemsForResettingCatalog = rebindContext.getCatalogItems();
+                    needsInitialCatalog = false;
+                } else {
+                    if (catInit.hasRun()) {
+                        logRebindingDebug("RebindManager will re-add any new items (persisted state empty)");
+                        needsInitialCatalog = false;
+                    } else {
+                        logRebindingDebug("RebindManager loading initial catalog locally because persisted state empty");
+                        needsInitialCatalog = true;
+                    }
                 }
             }
-            // TODO destroy old (as above)
         } else {
-            logRebindingDebug("RebindManager not resetting catalog because catalog persistence is disabled");
+            if (catInit.hasRun()) {
+                logRebindingDebug("RebindManager skipping catalog init because it has already run (catalog persistence disabled)");
+                needsInitialCatalog = false;
+            } else {
+                logRebindingDebug("RebindManager will initialize catalog locally because catalog persistence is disabled");
+                needsInitialCatalog = true;
+            }
         }
+
+        // TODO in read-only mode, perhaps do this less frequently than entities etc ?
+        catInit.populateCatalog(managementContext, needsInitialCatalog, itemsForResettingCatalog);
     }
 
     protected void instantiateLocationsAndEntities() {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java b/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
index 0f20f6b..de3460b 100644
--- a/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
@@ -38,11 +38,10 @@ import org.slf4j.LoggerFactory;
 import brooklyn.basic.BrooklynObject;
 import brooklyn.catalog.BrooklynCatalog;
 import brooklyn.catalog.CatalogItem;
-import brooklyn.catalog.CatalogLoadMode;
 import brooklyn.catalog.internal.BasicBrooklynCatalog;
+import brooklyn.catalog.internal.CatalogInitialization;
 import brooklyn.catalog.internal.CatalogUtils;
 import brooklyn.config.BrooklynProperties;
-import brooklyn.config.BrooklynServerConfig;
 import brooklyn.config.StringConfigMap;
 import brooklyn.entity.Effector;
 import brooklyn.entity.Entity;
@@ -170,6 +169,7 @@ public abstract class AbstractManagementContext implements ManagementContextInte
     protected final List<Throwable> errors = Collections.synchronizedList(MutableList.<Throwable>of()); 
 
     protected Maybe<URI> uri = Maybe.absent();
+    protected CatalogInitialization catalogInitialization;
 
     public AbstractManagementContext(BrooklynProperties brooklynProperties){
         this(brooklynProperties, null);
@@ -359,24 +359,28 @@ public abstract class AbstractManagementContext implements ManagementContextInte
     }
 
     @Override
-    public BrooklynCatalog getCatalog() {
-        if (catalog==null) {
-            loadCatalog();
-        }
-        return catalog;
-    }
-
-    protected synchronized void loadCatalog() {
-        if (catalog != null) return;
-        BasicBrooklynCatalog catalog = new BasicBrooklynCatalog(this);
-        CatalogLoadMode loadMode = getConfig().getConfig(BrooklynServerConfig.CATALOG_LOAD_MODE);
-        if (CatalogLoadMode.LOAD_BROOKLYN_CATALOG_URL.equals(loadMode)) {
-            log.debug("Resetting catalog to configured URL. Catalog mode is: {}", loadMode.name());
-            catalog.resetCatalogToContentsAtConfiguredUrl();
-        } else if (log.isDebugEnabled()) {
-            log.debug("Deferring catalog load to rebind manager. Catalog mode is: {}", loadMode.name());
+    public Maybe<BrooklynCatalog> getCatalogIfSet() {
+        return Maybe.<BrooklynCatalog>fromNullable(catalog);
+    }
+
+    @Override
+    public void setCatalog(BrooklynCatalog catalog) {
+        if (this.catalog!=null && catalog!=null) {
+            // should only happen if process has accessed catalog before rebind/startup populated it
+            log.warn("Replacing catalog in management context; new catalog is: "+catalog);
         }
-        this.catalog = catalog;
+        this.catalog = (BasicBrooklynCatalog) catalog;
+    }
+
+    @Override
+    public BrooklynCatalog getCatalog() {
+        if (catalog!=null) 
+            return catalog;
+        
+        // catalog init is needed; normally this will be done from start sequence,
+        // but if accessed early -- and in tests -- we will load it here
+        // TODO log if in launcher mode
+        return getCatalogInitialization().getCatalogPopulatingBestEffort(this);
     }
 
     /**
@@ -443,6 +447,21 @@ public abstract class AbstractManagementContext implements ManagementContextInte
         return uri;
     }
     
+    @Override
+    public CatalogInitialization getCatalogInitialization() {
+        if (catalogInitialization!=null) return catalogInitialization;
+        synchronized (this) {
+            if (catalogInitialization!=null) return catalogInitialization;
+            CatalogInitialization ci = new CatalogInitialization();
+            setCatalogInitialization(ci);
+            return ci;
+        }
+    }
+    
+    @Override
+    public synchronized void setCatalogInitialization(CatalogInitialization catalogInitialization) {
+        this.catalogInitialization = catalogInitialization;
+    }
     
     public BrooklynObject lookup(String id) {
         return lookup(id, BrooklynObject.class);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/core/src/main/java/brooklyn/management/internal/ManagementContextInternal.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/ManagementContextInternal.java b/core/src/main/java/brooklyn/management/internal/ManagementContextInternal.java
index 87bcc98..d5123a6 100644
--- a/core/src/main/java/brooklyn/management/internal/ManagementContextInternal.java
+++ b/core/src/main/java/brooklyn/management/internal/ManagementContextInternal.java
@@ -24,9 +24,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 
+import brooklyn.catalog.BrooklynCatalog;
+import brooklyn.catalog.internal.CatalogInitialization;
 import brooklyn.config.BrooklynProperties;
-import brooklyn.config.BrooklynServerConfig;
-import brooklyn.config.ConfigKey;
 import brooklyn.entity.Effector;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.BrooklynTaskTags;
@@ -51,7 +51,7 @@ public interface ManagementContextInternal extends ManagementContext {
     public static final String NON_TRANSIENT_TASK_TAG = BrooklynTaskTags.NON_TRANSIENT_TASK_TAG;
     public static final String TRANSIENT_TASK_TAG = BrooklynTaskTags.TRANSIENT_TASK_TAG;
 
-    public static final ConfigKey<String> BROOKLYN_CATALOG_URL = BrooklynServerConfig.BROOKLYN_CATALOG_URL;
+    public static final String EMPTY_CATALOG_URL = "classpath://brooklyn/empty.catalog.bom";
     
     ClassLoader getBaseClassLoader();
 
@@ -112,5 +112,18 @@ public interface ManagementContextInternal extends ManagementContext {
      * TODO In future this will change to a custom interface with a unique identifier for each error. */
     @Beta
     List<Throwable> errors();
+
+    @Beta
+    CatalogInitialization getCatalogInitialization();
+
+    @Beta
+    void setCatalogInitialization(CatalogInitialization catalogInitialization);
+
+    @Beta
+    public Maybe<BrooklynCatalog> getCatalogIfSet();
     
+    /** For use from {@link CatalogInitialization} to set the catalog */
+    @Beta
+    public void setCatalog(BrooklynCatalog catalog);
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
index d5fcf75..77eb4de 100644
--- a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
@@ -35,6 +35,8 @@ import org.slf4j.LoggerFactory;
 
 import brooklyn.basic.BrooklynObject;
 import brooklyn.catalog.BrooklynCatalog;
+import brooklyn.catalog.internal.BasicBrooklynCatalog;
+import brooklyn.catalog.internal.CatalogInitialization;
 import brooklyn.config.BrooklynProperties;
 import brooklyn.config.StringConfigMap;
 import brooklyn.entity.Application;
@@ -73,6 +75,7 @@ import brooklyn.mementos.BrooklynMementoRawData;
 import brooklyn.util.guava.Maybe;
 import brooklyn.util.time.Duration;
 
+import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
 
 public class NonDeploymentManagementContext implements ManagementContextInternal {
@@ -447,6 +450,31 @@ public class NonDeploymentManagementContext implements ManagementContextInternal
         return initialManagementContext.errors();
     }
 
+    @Override
+    public CatalogInitialization getCatalogInitialization() {
+        checkInitialManagementContextReal();
+        return initialManagementContext.getCatalogInitialization();
+    }
+    
+    @Override
+    public void setCatalogInitialization(CatalogInitialization catalogInitialization) {
+        checkInitialManagementContextReal();
+        initialManagementContext.setCatalogInitialization(catalogInitialization);
+    }
+
+    @Override
+    public Maybe<BrooklynCatalog> getCatalogIfSet() {
+        checkInitialManagementContextReal();
+        return initialManagementContext.getCatalogIfSet();
+    }
+    
+    /** For use from {@link CatalogInitialization} to set the catalog */
+    @Beta @Override
+    public void setCatalog(BrooklynCatalog catalog) {
+        checkInitialManagementContextReal();
+        initialManagementContext.setCatalog(catalog);
+    }
+    
     /**
      * For when the initial management context is not "real"; the changeListener is a no-op, but everything else forbidden.
      * 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/core/src/main/resources/brooklyn/empty.catalog.bom
----------------------------------------------------------------------
diff --git a/core/src/main/resources/brooklyn/empty.catalog.bom b/core/src/main/resources/brooklyn/empty.catalog.bom
new file mode 100644
index 0000000..401d2fa
--- /dev/null
+++ b/core/src/main/resources/brooklyn/empty.catalog.bom
@@ -0,0 +1,18 @@
+# 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.
+#
+brooklyn.catalog: {}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/core/src/test/java/brooklyn/catalog/internal/CatalogScanTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/catalog/internal/CatalogScanTest.java b/core/src/test/java/brooklyn/catalog/internal/CatalogScanTest.java
index 91372f0..949877f 100644
--- a/core/src/test/java/brooklyn/catalog/internal/CatalogScanTest.java
+++ b/core/src/test/java/brooklyn/catalog/internal/CatalogScanTest.java
@@ -32,6 +32,7 @@ import brooklyn.catalog.CatalogItem;
 import brooklyn.catalog.CatalogPredicates;
 import brooklyn.catalog.internal.MyCatalogItems.MySillyAppTemplate;
 import brooklyn.config.BrooklynProperties;
+import brooklyn.config.BrooklynServerConfig;
 import brooklyn.entity.Application;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.proxying.EntitySpec;
@@ -68,7 +69,7 @@ public class CatalogScanTest {
     private synchronized void loadFullCatalog() {
         if (fullCatalog!=null) return;
         BrooklynProperties props = BrooklynProperties.Factory.newEmpty();
-        props.put(LocalManagementContext.BROOKLYN_CATALOG_URL.getName(), 
+        props.put(BrooklynServerConfig.BROOKLYN_CATALOG_URL.getName(), 
                 "data:,"+Urls.encode("<catalog><classpath scan=\"types\"/></catalog>"));
         fullCatalog = newManagementContext(props).getCatalog();        
         log.info("ENTITIES loaded for FULL: "+fullCatalog.getCatalogItems(Predicates.alwaysTrue()));
@@ -77,7 +78,7 @@ public class CatalogScanTest {
     private synchronized void loadTheDefaultCatalog() {
         if (defaultCatalog!=null) return;
         BrooklynProperties props = BrooklynProperties.Factory.newEmpty();
-        props.put(LocalManagementContext.BROOKLYN_CATALOG_URL.getName(), "");
+        props.put(BrooklynServerConfig.BROOKLYN_CATALOG_URL.getName(), "");
         LocalManagementContext managementContext = newManagementContext(props);
         defaultCatalog = managementContext.getCatalog();        
         log.info("ENTITIES loaded for DEFAULT: "+defaultCatalog.getCatalogItems(Predicates.alwaysTrue()));
@@ -87,7 +88,7 @@ public class CatalogScanTest {
     private synchronized void loadAnnotationsOnlyCatalog() {
         if (annotsCatalog!=null) return;
         BrooklynProperties props = BrooklynProperties.Factory.newEmpty();
-        props.put(LocalManagementContext.BROOKLYN_CATALOG_URL.getName(),
+        props.put(BrooklynServerConfig.BROOKLYN_CATALOG_URL.getName(),
                 "data:,"+URLEncoder.encode("<catalog><classpath scan=\"annotations\"/></catalog>"));
         LocalManagementContext managementContext = newManagementContext(props);
         annotsCatalog = managementContext.getCatalog();
@@ -126,14 +127,14 @@ public class CatalogScanTest {
     @Test
     public void testAnnotationLoadsSomeApps() {
         loadAnnotationsOnlyCatalog();
-        Iterable<CatalogItem<Object,Object>> silly1 = annotsCatalog.getCatalogItems(CatalogPredicates.name(Predicates.equalTo("MySillyAppTemplate")));
+        Iterable<CatalogItem<Object,Object>> silly1 = annotsCatalog.getCatalogItems(CatalogPredicates.displayName(Predicates.equalTo("MySillyAppTemplate")));
         Assert.assertEquals(Iterables.getOnlyElement(silly1).getDescription(), "Some silly app test");
     }
     
     @Test
     public void testAnnotationLoadsSomeAppBuilders() {
         loadAnnotationsOnlyCatalog();
-        Iterable<CatalogItem<Object,Object>> silly1 = annotsCatalog.getCatalogItems(CatalogPredicates.name(Predicates.equalTo("MySillyAppBuilderTemplate")));
+        Iterable<CatalogItem<Object,Object>> silly1 = annotsCatalog.getCatalogItems(CatalogPredicates.displayName(Predicates.equalTo("MySillyAppBuilderTemplate")));
         Assert.assertEquals(Iterables.getOnlyElement(silly1).getDescription(), "Some silly app builder test");
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/core/src/test/java/brooklyn/entity/rebind/RebindCatalogEntityTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogEntityTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogEntityTest.java
index cf9e5fe..9c94697 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogEntityTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogEntityTest.java
@@ -22,10 +22,10 @@ import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNotSame;
 
+import java.io.Closeable;
 import java.net.URL;
 import java.util.List;
 
-import brooklyn.test.TestResourceUnavailableException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.annotations.BeforeMethod;
@@ -40,10 +40,13 @@ import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.StartableApplication;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.event.basic.Sensors;
+import brooklyn.management.ManagementContext;
 import brooklyn.management.ha.ManagementNodeState;
 import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.test.TestResourceUnavailableException;
 import brooklyn.util.javalang.UrlClassLoader;
-import brooklyn.util.os.Os;
+
+import com.google.common.base.Function;
 
 public class RebindCatalogEntityTest extends RebindTestFixture<StartableApplication> {
 
@@ -89,6 +92,8 @@ public class RebindCatalogEntityTest extends RebindTestFixture<StartableApplicat
     //
     // Note: to test before/after behaviour (i.e. that we're really fixing what we think we are) then comment out the body of:
     //       AbstractMemento.injectTypeClass(Class)
+    //
+    // NB: this behaviour is generally deprecated in favour of OSGi now.
     @Test
     public void testRestoresAppFromCatalogClassloader() throws Exception {
         @SuppressWarnings("unchecked")
@@ -100,7 +105,7 @@ public class RebindCatalogEntityTest extends RebindTestFixture<StartableApplicat
         origApp = ApplicationBuilder.newManagedApp(appSpec, origManagementContext);
         ((EntityInternal)origApp).setAttribute(Sensors.newStringSensor("mysensor"), "mysensorval");
         
-        newApp = rebind();
+        newApp = rebindWithAppClass();
         Entities.dumpInfo(newApp);
         assertNotSame(newApp, origApp);
         assertEquals(newApp.getId(), origApp.getId());
@@ -118,17 +123,27 @@ public class RebindCatalogEntityTest extends RebindTestFixture<StartableApplicat
     // TODO Not using RebindTestUtils.rebind(mementoDir, getClass().getClassLoader());
     //      because that won't have right catalog classpath.
     //      How to reuse that code cleanly?
-    @Override
-    protected StartableApplication rebind() throws Exception {
+    protected StartableApplication rebindWithAppClass() throws Exception {
         RebindTestUtils.waitForPersisted(origApp);
-
         LocalManagementContext newManagementContext = RebindTestUtils.newPersistingManagementContextUnstarted(mementoDir, classLoader);
-        
+
+        UrlClassLoader ucl = new UrlClassLoader(url);
         @SuppressWarnings("unchecked")
-        Class<? extends AbstractApplication> appClazz = (Class<? extends AbstractApplication>) new UrlClassLoader(url).loadClass(APP_CLASSNAME);
-        newManagementContext.getCatalog().addItem(appClazz);
+        final Class<? extends AbstractApplication> appClazz = (Class<? extends AbstractApplication>) ucl.loadClass(APP_CLASSNAME);
+        // ucl.close is only introduced in java 1.7
+        if (ucl instanceof Closeable) ((Closeable)ucl).close();
+
+        newManagementContext.getCatalogInitialization().addPopulationCallback(new Function<ManagementContext, Void>() {
+            @Override
+            public Void apply(ManagementContext input) {
+                input.getCatalog().addItem(appClazz);
+                return null;
+            }
+        });
         
         ClassLoader classLoader = newManagementContext.getCatalog().getRootClassLoader();
+        
+        classLoader.loadClass(appClazz.getName());
         List<Application> newApps = newManagementContext.getRebindManager().rebind(classLoader, null, ManagementNodeState.MASTER);
         newManagementContext.getRebindManager().startPersistence();
         return (StartableApplication) newApps.get(0);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
index e0260c3..10bcc68 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
@@ -37,7 +37,6 @@ import brooklyn.camp.lite.CampPlatformWithJustBrooklynMgmt;
 import brooklyn.camp.lite.TestAppAssemblyInstantiator;
 import brooklyn.catalog.CatalogItem;
 import brooklyn.catalog.CatalogItem.CatalogItemType;
-import brooklyn.catalog.CatalogLoadMode;
 import brooklyn.catalog.internal.BasicBrooklynCatalog;
 import brooklyn.catalog.internal.CatalogDto;
 import brooklyn.config.BrooklynProperties;
@@ -85,7 +84,7 @@ public class RebindCatalogItemTest extends RebindTestFixtureWithApp {
     protected LocalManagementContext createOrigManagementContext() {
         BrooklynProperties properties = BrooklynProperties.Factory.newDefault();
         properties.put(BrooklynServerConfig.BROOKLYN_CATALOG_URL, "classpath://brooklyn/entity/rebind/rebind-catalog-item-test-catalog.xml");
-        properties.put(BrooklynServerConfig.CATALOG_LOAD_MODE, CatalogLoadMode.LOAD_BROOKLYN_CATALOG_URL);
+        properties.put(BrooklynServerConfig.CATALOG_LOAD_MODE, brooklyn.catalog.CatalogLoadMode.LOAD_BROOKLYN_CATALOG_URL);
         return RebindTestUtils.managementContextBuilder(mementoDir, classLoader)
                 .properties(properties)
                 .persistPeriodMillis(getPersistPeriodMillis())
@@ -97,7 +96,6 @@ public class RebindCatalogItemTest extends RebindTestFixtureWithApp {
     protected LocalManagementContext createNewManagementContext(File mementoDir) {
         BrooklynProperties properties = BrooklynProperties.Factory.newDefault();
         properties.put(BrooklynServerConfig.BROOKLYN_CATALOG_URL, "classpath://brooklyn/entity/rebind/rebind-catalog-item-test-catalog.xml");
-        properties.put(BrooklynServerConfig.CATALOG_LOAD_MODE, CatalogLoadMode.LOAD_BROOKLYN_CATALOG_URL_IF_NO_PERSISTED_STATE);
         return RebindTestUtils.managementContextBuilder(mementoDir, classLoader)
                 .properties(properties)
                 .forLive(useLiveManagementContext())

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/core/src/test/java/brooklyn/entity/rebind/RebindTestUtils.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindTestUtils.java b/core/src/test/java/brooklyn/entity/rebind/RebindTestUtils.java
index ea901af..939d42e 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindTestUtils.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindTestUtils.java
@@ -200,7 +200,7 @@ public class RebindTestUtils {
                     ? this.properties
                     : BrooklynProperties.Factory.newDefault();
             if (this.emptyCatalog) {
-                properties.putIfAbsent(BrooklynServerConfig.BROOKLYN_CATALOG_URL, "classpath://brooklyn-catalog-empty.xml");
+                properties.putIfAbsent(BrooklynServerConfig.BROOKLYN_CATALOG_URL, ManagementContextInternal.EMPTY_CATALOG_URL);
             }
             if (forLive) {
                 unstarted = new LocalManagementContext(properties);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/core/src/test/java/brooklyn/test/entity/LocalManagementContextForTests.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/test/entity/LocalManagementContextForTests.java b/core/src/test/java/brooklyn/test/entity/LocalManagementContextForTests.java
index 38d163a..28b6914 100644
--- a/core/src/test/java/brooklyn/test/entity/LocalManagementContextForTests.java
+++ b/core/src/test/java/brooklyn/test/entity/LocalManagementContextForTests.java
@@ -22,6 +22,7 @@ import brooklyn.config.BrooklynProperties;
 import brooklyn.config.BrooklynServerConfig;
 import brooklyn.config.ConfigKey;
 import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.management.internal.ManagementContextInternal;
 
 /** management context which allows disabling common time-consuming tasks.
  * most instances have:
@@ -55,7 +56,7 @@ public class LocalManagementContextForTests extends LocalManagementContext {
 
     public static BrooklynProperties setEmptyCatalogAsDefault(BrooklynProperties brooklynProperties) {
         if (brooklynProperties==null) return null;
-        brooklynProperties.putIfAbsent(BrooklynServerConfig.BROOKLYN_CATALOG_URL, "classpath://brooklyn-catalog-empty.xml");
+        brooklynProperties.putIfAbsent(BrooklynServerConfig.BROOKLYN_CATALOG_URL, ManagementContextInternal.EMPTY_CATALOG_URL);
         return brooklynProperties;
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/usage/cli/src/main/java/brooklyn/cli/Main.java
----------------------------------------------------------------------
diff --git a/usage/cli/src/main/java/brooklyn/cli/Main.java b/usage/cli/src/main/java/brooklyn/cli/Main.java
index d89703e..7dab5aa 100644
--- a/usage/cli/src/main/java/brooklyn/cli/Main.java
+++ b/usage/cli/src/main/java/brooklyn/cli/Main.java
@@ -41,6 +41,7 @@ import brooklyn.BrooklynVersion;
 import brooklyn.basic.BrooklynTypes;
 import brooklyn.catalog.BrooklynCatalog;
 import brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.internal.CatalogInitialization;
 import brooklyn.cli.CloudExplorer.BlobstoreGetBlobCommand;
 import brooklyn.cli.CloudExplorer.BlobstoreListContainerCommand;
 import brooklyn.cli.CloudExplorer.BlobstoreListContainersCommand;
@@ -217,6 +218,24 @@ public class Main extends AbstractMain {
                         "(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, "
+                + "if nothing is yet persisted in the catalog (or if it is reset)")
+        public String catalogInitial;
+
+        @Option(name = { "--catalogReset" }, title = "clear catalog",
+            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" }, title = "force catalog addition",
+            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 = "Specifies the port to be used by the Brooklyn Management Console; "
                     + "default is 8081+ for http, 8443+ for https.")
@@ -386,13 +405,15 @@ public class Main extends AbstractMain {
     
                 launcher = createLauncher();
 
-                launcher.customizeInitialCatalog(new Function<BrooklynLauncher,Void>() {
+                CatalogInitialization catInit = new CatalogInitialization(catalogInitial, catalogReset, catalogAdd, catalogForce);
+                catInit.addPopulationCallback(new Function<ManagementContext,Void>() {
                     @Override
-                    public Void apply(BrooklynLauncher launcher) {
-                        populateCatalog(launcher.getServerDetails().getManagementContext().getCatalog());
+                    public Void apply(ManagementContext mgmt) {
+                        populateCatalog(mgmt.getCatalog());
                         return null;
                     }
                 });
+                launcher.catalogInitialization(catInit);
                 
                 launcher.persistMode(persistMode);
                 launcher.persistenceDir(persistenceDir);
@@ -652,7 +673,7 @@ public class Main extends AbstractMain {
                 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 issued. Shutdown via GUI or API or process interrupt.");
+                log.info("Launched Brooklyn; will now block until shutdown command received via GUI/API (recommended) or process interrupt.");
                 waitUntilInterrupted();
             }
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
index 16f4f76..4d3b1be 100644
--- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
+++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
@@ -41,7 +41,7 @@ import javax.annotation.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import brooklyn.catalog.CatalogLoadMode;
+import brooklyn.catalog.internal.CatalogInitialization;
 import brooklyn.config.BrooklynProperties;
 import brooklyn.config.BrooklynServerConfig;
 import brooklyn.config.BrooklynServerPaths;
@@ -66,7 +66,6 @@ import brooklyn.entity.rebind.persister.PersistMode;
 import brooklyn.entity.rebind.persister.PersistenceObjectStore;
 import brooklyn.entity.rebind.transformer.CompoundTransformer;
 import brooklyn.entity.trait.Startable;
-import brooklyn.internal.BrooklynFeatureEnablement;
 import brooklyn.launcher.config.StopWhichAppsOnShutdown;
 import brooklyn.location.Location;
 import brooklyn.location.LocationSpec;
@@ -158,7 +157,7 @@ public class BrooklynLauncher {
     private StopWhichAppsOnShutdown stopWhichAppsOnShutdown = StopWhichAppsOnShutdown.THESE_IF_NOT_PERSISTED;
     
     private Function<ManagementContext,Void> customizeManagement = null;
-    private Function<BrooklynLauncher,Void> customizeInitialCatalog = null;
+    private CatalogInitialization catalogInitialization = null;
     
     private PersistMode persistMode = PersistMode.DISABLED;
     private HighAvailabilityMode highAvailabilityMode = HighAvailabilityMode.DISABLED;
@@ -423,10 +422,10 @@ public class BrooklynLauncher {
     }
 
     @Beta
-    public BrooklynLauncher customizeInitialCatalog(Function<BrooklynLauncher, Void> customizeInitialCatalog) {
-        if (this.customizeInitialCatalog!=null)
+    public BrooklynLauncher catalogInitialization(CatalogInitialization catInit) {
+        if (this.catalogInitialization!=null)
             throw new IllegalStateException("Initial catalog customization already set.");
-        this.customizeInitialCatalog = customizeInitialCatalog;
+        this.catalogInitialization = catInit;
         return this;
     }
 
@@ -560,8 +559,6 @@ public class BrooklynLauncher {
         if (started) throw new IllegalStateException("Cannot start() or launch() multiple times");
         started = true;
 
-        setCatalogLoadMode();
-
         // Create the management context
         initManagementContext();
 
@@ -589,10 +586,12 @@ public class BrooklynLauncher {
         }
 
         try {
-            // TODO currently done *after* above to mirror existing usage, 
-            // but where this runs will likely change
-            if (customizeInitialCatalog!=null)
-                customizeInitialCatalog.apply(this);
+            // run cat init now if it hasn't yet been run
+            CatalogInitialization catInit = ((ManagementContextInternal)managementContext).getCatalogInitialization();
+            if (catInit!=null && !catInit.hasRun()) {
+                LOG.debug("Loading catalog as part of launcher (persistence did not run it)");
+                catInit.populateCatalog(managementContext, true, null);
+            }
         } catch (Exception e) {
             handleSubsystemStartupError(true, "initial catalog", e);
         }
@@ -622,22 +621,6 @@ public class BrooklynLauncher {
         return this;
     }
 
-    /**
-     * Sets {@link BrooklynServerConfig#CATALOG_LOAD_MODE} in {@link #brooklynAdditionalProperties}.
-     * <p>
-     * Checks {@link brooklyn.internal.BrooklynFeatureEnablement#FEATURE_CATALOG_PERSISTENCE_PROPERTY}
-     * and the {@link #persistMode persistence mode}.
-     */
-    private void setCatalogLoadMode() {
-        CatalogLoadMode catalogLoadMode;
-        if (!BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_CATALOG_PERSISTENCE_PROPERTY)) {
-            catalogLoadMode = CatalogLoadMode.LOAD_BROOKLYN_CATALOG_URL;
-        } else {
-            catalogLoadMode = CatalogLoadMode.forPersistMode(persistMode);
-        }
-        brooklynProperties(BrooklynServerConfig.CATALOG_LOAD_MODE, catalogLoadMode);
-    }
-
     private void initManagementContext() {
         // Create the management context
         if (managementContext == null) {
@@ -682,6 +665,8 @@ public class BrooklynLauncher {
             brooklynProperties.addFromMap(brooklynAdditionalProperties);
         }
         
+        ((ManagementContextInternal)managementContext).setCatalogInitialization(catalogInitialization);
+        
         if (customizeManagement!=null) {
             customizeManagement.apply(managementContext);
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherTest.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherTest.java b/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherTest.java
index 0eba3d7..b4f963e 100644
--- a/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherTest.java
+++ b/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherTest.java
@@ -36,6 +36,7 @@ import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.Test;
 
+import brooklyn.catalog.internal.CatalogInitialization;
 import brooklyn.config.BrooklynProperties;
 import brooklyn.config.BrooklynServerConfig;
 import brooklyn.entity.Application;
@@ -313,15 +314,15 @@ public class BrooklynLauncherTest {
         }
     }
     
-    @Test  // takes a few seconds because starts webapp, but also tests rest api so useful
+    @Test  // takes a bit of time because starts webapp, but also tests rest api so useful
     public void testErrorsCaughtByApiAndRestApiWorks() throws Exception {
         launcher = newLauncherForTests(true)
-                .customizeInitialCatalog(new Function<BrooklynLauncher, Void>() {
+                .catalogInitialization(new CatalogInitialization(null, false, null, false).addPopulationCallback(new Function<ManagementContext, Void>() {
                     @Override
-                    public Void apply(BrooklynLauncher input) {
+                    public Void apply(ManagementContext input) {
                         throw new RuntimeException("deliberate-exception-for-testing");
                     }
-                })
+                }))
                 .start();
         // such an error should be thrown, then caught in this calling thread
         ManagementContext mgmt = launcher.getServerDetails().getManagementContext();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncher.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncher.java b/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncher.java
index 9941466..f189815 100644
--- a/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncher.java
+++ b/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncher.java
@@ -43,6 +43,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import brooklyn.config.BrooklynProperties;
+import brooklyn.config.BrooklynServerConfig;
 import brooklyn.config.BrooklynServiceAttributes;
 import brooklyn.management.ManagementContext;
 import brooklyn.management.internal.LocalManagementContext;
@@ -200,7 +201,7 @@ public class BrooklynRestApiLauncher {
 
         if (forceUseOfDefaultCatalogWithJavaClassPath) {
             // don't use any catalog.xml which is set
-            ((BrooklynProperties) mgmt.getConfig()).put(ManagementContextInternal.BROOKLYN_CATALOG_URL, "");
+            ((BrooklynProperties) mgmt.getConfig()).put(BrooklynServerConfig.BROOKLYN_CATALOG_URL, ManagementContextInternal.EMPTY_CATALOG_URL);
             // sets URLs for a surefire
             ((LocalManagementContext) mgmt).setBaseClassPathForScanning(ClasspathHelper.forJavaClassPath());
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0b9bc3b3/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java b/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
index 4ce519f..86be4c7 100644
--- a/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
+++ b/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
@@ -25,6 +25,7 @@ import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 
 import brooklyn.config.BrooklynProperties;
+import brooklyn.config.BrooklynServerConfig;
 import brooklyn.config.BrooklynServiceAttributes;
 import brooklyn.entity.basic.Entities;
 import brooklyn.management.ManagementContext;
@@ -82,7 +83,7 @@ public abstract class BrooklynRestApiLauncherTestFixture {
 
     public static void forceUseOfDefaultCatalogWithJavaClassPath(ManagementContext manager) {
         // don't use any catalog.xml which is set
-        ((BrooklynProperties)manager.getConfig()).put(ManagementContextInternal.BROOKLYN_CATALOG_URL, "");
+        ((BrooklynProperties)manager.getConfig()).put(BrooklynServerConfig.BROOKLYN_CATALOG_URL, ManagementContextInternal.EMPTY_CATALOG_URL);
         // sets URLs for a surefire
         ((LocalManagementContext)manager).setBaseClassPathForScanning(ClasspathHelper.forJavaClassPath());
         // this also works