You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2014/11/27 08:12:08 UTC

[25/39] cayenne git commit: CAY-1972 A property to override DataSources of multi-module projects

CAY-1972 A property to override DataSources of multi-module projects


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/15297cca
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/15297cca
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/15297cca

Branch: refs/heads/CAY-1946_1
Commit: 15297ccafa761bddb89eac98b35806d777cbbe74
Parents: 3c51a77
Author: aadamchik <aa...@apache.org>
Authored: Sat Nov 22 18:06:09 2014 +0300
Committer: aadamchik <aa...@apache.org>
Committed: Sat Nov 22 19:03:31 2014 +0300

----------------------------------------------------------------------
 .../apache/cayenne/configuration/Constants.java | 298 ++++++++++---------
 .../server/DataDomainProvider.java              | 241 ++++++++-------
 .../server/ServerRuntimeBuilder.java            |  52 +++-
 .../server/SyntheticNodeDataDomainProvider.java |  27 +-
 .../server/ServerRuntimeBuilderIT.java          | 109 +++++++
 .../server/ServerRuntimeBuilderTest.java        |  53 +++-
 .../ServerRuntimeBuilder_InAction_IT.java       |  88 ------
 docs/doc/src/main/resources/RELEASE-NOTES.txt   |   1 +
 docs/doc/src/main/resources/UPGRADE.txt         |   8 +
 9 files changed, 491 insertions(+), 386 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/15297cca/cayenne-server/src/main/java/org/apache/cayenne/configuration/Constants.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/Constants.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/Constants.java
index 12953e6..1b0f887 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/Constants.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/Constants.java
@@ -26,151 +26,159 @@ package org.apache.cayenne.configuration;
  */
 public interface Constants {
 
-    // DI "collections"
+	// DI "collections"
+
+	/**
+	 * A DI container key for the Map&lt;String, String&gt; storing properties
+	 * used by built-in Cayenne service.
+	 */
+	public static final String PROPERTIES_MAP = "cayenne.properties";
+
+	/**
+	 * A DI container key for the List&lt;DbAdapterDetector&gt; that contains
+	 * objects that can discover the type of current database and install the
+	 * correct DbAdapter in runtime.
+	 */
+	public static final String SERVER_ADAPTER_DETECTORS_LIST = "cayenne.server.adapter_detectors";
 
-    /**
-     * A DI container key for the Map&lt;String, String&gt; storing properties
-     * used by built-in Cayenne service.
-     */
-    public static final String PROPERTIES_MAP = "cayenne.properties";
-
-    /**
-     * A DI container key for the List&lt;DbAdapterDetector&gt; that contains
-     * objects that can discover the type of current database and install the
-     * correct DbAdapter in runtime.
-     */
-    public static final String SERVER_ADAPTER_DETECTORS_LIST = "cayenne.server.adapter_detectors";
-
-    /**
-     * A DI container key for the List&lt;DataChannelFilter&gt; storing
-     * DataDomain filters.
-     */
-    public static final String SERVER_DOMAIN_FILTERS_LIST = "cayenne.server.domain_filters";
-
-    /**
-     * A DI container key for the List&lt;String&gt; storing locations of the
-     * one of more project configuration files.
-     */
-    public static final String SERVER_PROJECT_LOCATIONS_LIST = "cayenne.server.project_locations";
-
-    /**
-     * A DI container key for the List&lt;ExtendedType&gt; storing default
-     * adapter-agnostic ExtendedTypes.
-     */
-    public static final String SERVER_DEFAULT_TYPES_LIST = "cayenne.server.default_types";
-
-    /**
-     * A DI container key for the List&lt;ExtendedType&gt; storing a
-     * user-provided ExtendedTypes.
-     */
-    public static final String SERVER_USER_TYPES_LIST = "cayenne.server.user_types";
-
-    /**
-     * A DI container key for the List&lt;ExtendedTypeFactory&gt; storing
-     * default and user-provided ExtendedTypeFactories.
-     */
-    public static final String SERVER_TYPE_FACTORIES_LIST = "cayenne.server.type_factories";
-
-    /**
-     * A server-side DI container key for the Map&lt;String, String&gt; storing
-     * event bridge properties passed to the ROP client on bootstrap.
-     */
-    public static final String SERVER_ROP_EVENT_BRIDGE_PROPERTIES_MAP = "cayenne.server.rop_event_bridge_properties";
-
-    // Runtime properties
-
-    public static final String JDBC_DRIVER_PROPERTY = "cayenne.jdbc.driver";
-
-    public static final String JDBC_URL_PROPERTY = "cayenne.jdbc.url";
-
-    public static final String JDBC_USERNAME_PROPERTY = "cayenne.jdbc.username";
-
-    public static final String JDBC_PASSWORD_PROPERTY = "cayenne.jdbc.password";
-
-    public static final String JDBC_MIN_CONNECTIONS_PROPERTY = "cayenne.jdbc.min_connections";
-
-    public static final String JDBC_MAX_CONNECTIONS_PROPERTY = "cayenne.jdbc.max_connections";
-
-    /**
-     * An integer property defining the maximum number of entries in the query
-     * cache. Note that not all QueryCache providers may respect this property.
-     * MapQueryCache uses it, but the rest would use alternative configuration
-     * methods.
-     */
-    public static final String QUERY_CACHE_SIZE_PROPERTY = "cayenne.querycache.size";
-
-    /**
-     * A boolean property defining whether cross-contexts synchronization is
-     * enabled. Possible values are "true" or "false".
-     */
-    public static final String SERVER_CONTEXTS_SYNC_PROPERTY = "cayenne.server.contexts_sync_strategy";
-
-    /**
-     * A String property that defines how ObjectContexts should retain cached
-     * committed objects. Possible values are "weak", "soft", "hard".
-     */
-    public static final String SERVER_OBJECT_RETAIN_STRATEGY_PROPERTY = "cayenne.server.object_retain_strategy";
-
-    /**
-     * A boolean property that defines whether runtime should use external
-     * transactions. Possible values are "true" or "false".
-     */
-    public static final String SERVER_EXTERNAL_TX_PROPERTY = "cayenne.server.external_tx";
-
-    public static final String ROP_SERVICE_URL_PROPERTY = "cayenne.rop.service_url";
-
-    public static final String ROP_SERVICE_USERNAME_PROPERTY = "cayenne.rop.service_username";
-
-    public static final String ROP_SERVICE_PASSWORD_PROPERTY = "cayenne.rop.service_password";
-
-    public static final String ROP_SERVICE_SHARED_SESSION_PROPERTY = "cayenne.rop.shared_session_name";
-
-    public static final String ROP_SERVICE_TIMEOUT_PROPERTY = "cayenne.rop.service_timeout";
-
-    public static final String ROP_CHANNEL_EVENTS_PROPERTY = "cayenne.rop.channel_events";
-
-    public static final String ROP_CONTEXT_CHANGE_EVENTS_PROPERTY = "cayenne.rop.context_change_events";
-
-    public static final String ROP_CONTEXT_LIFECYCLE_EVENTS_PROPERTY = "cayenne.rop.context_lifecycle_events";
-
-    /**
-     * The name of the {@link org.apache.cayenne.event.EventBridgeFactory} that
-     * is passed from the ROP server to the client. Client would instantiate the
-     * factory to receive events from the server. Note that this property is
-     * stored in {@link #SERVER_ROP_EVENT_BRIDGE_PROPERTIES_MAP}, not
-     * {@link #PROPERTIES_MAP}.
-     */
-    public static final String SERVER_ROP_EVENT_BRIDGE_FACTORY_PROPERTY = "cayenne.server.rop_event_bridge_factory";
-
-    /**
-     * A property that defines a maximum number of ID qualifiers in where clause
-     * of queries that are generated for example in
-     * {@link org.apache.cayenne.access.IncrementalFaultList} or in
-     * DISJOINT_BY_ID prefetch processing. This is needed to avoid where clause
-     * size limitations and memory usage efficiency.
-     */
-    public static final String SERVER_MAX_ID_QUALIFIER_SIZE_PROPERTY = "cayenne.server.max_id_qualifier_size";
-
-    /**
-     * Defines a maximum time in milliseconds that a connection request could
-     * wait in the connection queue. After this period expires, an exception
-     * will be thrown in the calling method. A value of zero will make the
-     * thread wait until a connection is available with no time out. Defaults to
-     * 20 seconds.
-     */
-    public static final String SERVER_MAX_QUEUE_WAIT_TIME = "cayenne.jdbc.max_wait";
-
-    /** Defines if database uses case-insensitive collation */
-    public final static String CI_PROPERTY = "cayenne.runtime.db.collation.assume.ci";
-
-    /**
-     * A integer property that enables logging for just long running queries
-     * (rather than all queries). The value is the minimum number of
-     * milliseconds a query must run before is logged. A value less than or
-     * equal to zero (the default) disables this feature.
-     * 
-     * @since 4.0
-     * */
-    public final static String QUERY_EXECUTION_TIME_LOGGING_THRESHOLD_PROPERTY = "cayenne.server.query_execution_time_logging_threshold";
+	/**
+	 * A DI container key for the List&lt;DataChannelFilter&gt; storing
+	 * DataDomain filters.
+	 */
+	public static final String SERVER_DOMAIN_FILTERS_LIST = "cayenne.server.domain_filters";
+
+	/**
+	 * A DI container key for the List&lt;String&gt; storing locations of the
+	 * one of more project configuration files.
+	 */
+	public static final String SERVER_PROJECT_LOCATIONS_LIST = "cayenne.server.project_locations";
+
+	/**
+	 * A DI container key for the List&lt;ExtendedType&gt; storing default
+	 * adapter-agnostic ExtendedTypes.
+	 */
+	public static final String SERVER_DEFAULT_TYPES_LIST = "cayenne.server.default_types";
+
+	/**
+	 * A DI container key for the List&lt;ExtendedType&gt; storing a
+	 * user-provided ExtendedTypes.
+	 */
+	public static final String SERVER_USER_TYPES_LIST = "cayenne.server.user_types";
+
+	/**
+	 * A DI container key for the List&lt;ExtendedTypeFactory&gt; storing
+	 * default and user-provided ExtendedTypeFactories.
+	 */
+	public static final String SERVER_TYPE_FACTORIES_LIST = "cayenne.server.type_factories";
+
+	/**
+	 * A server-side DI container key for the Map&lt;String, String&gt; storing
+	 * event bridge properties passed to the ROP client on bootstrap.
+	 */
+	public static final String SERVER_ROP_EVENT_BRIDGE_PROPERTIES_MAP = "cayenne.server.rop_event_bridge_properties";
+
+	// Runtime properties
+
+	public static final String JDBC_DRIVER_PROPERTY = "cayenne.jdbc.driver";
+
+	public static final String JDBC_URL_PROPERTY = "cayenne.jdbc.url";
+
+	public static final String JDBC_USERNAME_PROPERTY = "cayenne.jdbc.username";
+
+	public static final String JDBC_PASSWORD_PROPERTY = "cayenne.jdbc.password";
+
+	public static final String JDBC_MIN_CONNECTIONS_PROPERTY = "cayenne.jdbc.min_connections";
+
+	public static final String JDBC_MAX_CONNECTIONS_PROPERTY = "cayenne.jdbc.max_connections";
+
+	/**
+	 * An integer property defining the maximum number of entries in the query
+	 * cache. Note that not all QueryCache providers may respect this property.
+	 * MapQueryCache uses it, but the rest would use alternative configuration
+	 * methods.
+	 */
+	public static final String QUERY_CACHE_SIZE_PROPERTY = "cayenne.querycache.size";
+
+	/**
+	 * An optional name of the runtime DataDomain. If not specified (which is
+	 * normally the case), the name is inferred from the configuration name.
+	 * 
+	 * @since 4.0
+	 */
+	public static final String SERVER_DOMAIN_NAME_PROPERTY = "cayenne.server.domain.name";
+
+	/**
+	 * A boolean property defining whether cross-contexts synchronization is
+	 * enabled. Possible values are "true" or "false".
+	 */
+	public static final String SERVER_CONTEXTS_SYNC_PROPERTY = "cayenne.server.contexts_sync_strategy";
+
+	/**
+	 * A String property that defines how ObjectContexts should retain cached
+	 * committed objects. Possible values are "weak", "soft", "hard".
+	 */
+	public static final String SERVER_OBJECT_RETAIN_STRATEGY_PROPERTY = "cayenne.server.object_retain_strategy";
+
+	/**
+	 * A boolean property that defines whether runtime should use external
+	 * transactions. Possible values are "true" or "false".
+	 */
+	public static final String SERVER_EXTERNAL_TX_PROPERTY = "cayenne.server.external_tx";
+
+	public static final String ROP_SERVICE_URL_PROPERTY = "cayenne.rop.service_url";
+
+	public static final String ROP_SERVICE_USERNAME_PROPERTY = "cayenne.rop.service_username";
+
+	public static final String ROP_SERVICE_PASSWORD_PROPERTY = "cayenne.rop.service_password";
+
+	public static final String ROP_SERVICE_SHARED_SESSION_PROPERTY = "cayenne.rop.shared_session_name";
+
+	public static final String ROP_SERVICE_TIMEOUT_PROPERTY = "cayenne.rop.service_timeout";
+
+	public static final String ROP_CHANNEL_EVENTS_PROPERTY = "cayenne.rop.channel_events";
+
+	public static final String ROP_CONTEXT_CHANGE_EVENTS_PROPERTY = "cayenne.rop.context_change_events";
+
+	public static final String ROP_CONTEXT_LIFECYCLE_EVENTS_PROPERTY = "cayenne.rop.context_lifecycle_events";
+
+	/**
+	 * The name of the {@link org.apache.cayenne.event.EventBridgeFactory} that
+	 * is passed from the ROP server to the client. Client would instantiate the
+	 * factory to receive events from the server. Note that this property is
+	 * stored in {@link #SERVER_ROP_EVENT_BRIDGE_PROPERTIES_MAP}, not
+	 * {@link #PROPERTIES_MAP}.
+	 */
+	public static final String SERVER_ROP_EVENT_BRIDGE_FACTORY_PROPERTY = "cayenne.server.rop_event_bridge_factory";
+
+	/**
+	 * A property that defines a maximum number of ID qualifiers in where clause
+	 * of queries that are generated for example in
+	 * {@link org.apache.cayenne.access.IncrementalFaultList} or in
+	 * DISJOINT_BY_ID prefetch processing. This is needed to avoid where clause
+	 * size limitations and memory usage efficiency.
+	 */
+	public static final String SERVER_MAX_ID_QUALIFIER_SIZE_PROPERTY = "cayenne.server.max_id_qualifier_size";
+
+	/**
+	 * Defines a maximum time in milliseconds that a connection request could
+	 * wait in the connection queue. After this period expires, an exception
+	 * will be thrown in the calling method. A value of zero will make the
+	 * thread wait until a connection is available with no time out. Defaults to
+	 * 20 seconds.
+	 */
+	public static final String SERVER_MAX_QUEUE_WAIT_TIME = "cayenne.jdbc.max_wait";
+
+	/** Defines if database uses case-insensitive collation */
+	public final static String CI_PROPERTY = "cayenne.runtime.db.collation.assume.ci";
+
+	/**
+	 * A integer property that enables logging for just long running queries
+	 * (rather than all queries). The value is the minimum number of
+	 * milliseconds a query must run before is logged. A value less than or
+	 * equal to zero (the default) disables this feature.
+	 * 
+	 * @since 4.0
+	 * */
+	public final static String QUERY_EXECUTION_TIME_LOGGING_THRESHOLD_PROPERTY = "cayenne.server.query_execution_time_logging_threshold";
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/15297cca/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DataDomainProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DataDomainProvider.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DataDomainProvider.java
index f1a8d57..4815fe4 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DataDomainProvider.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DataDomainProvider.java
@@ -55,173 +55,172 @@ import org.apache.commons.logging.LogFactory;
  */
 public class DataDomainProvider implements Provider<DataDomain> {
 
-    /**
-     * @since 4.0
-     */
-    static final String DEFAULT_NAME = "cayenne";
+	private static Log logger = LogFactory.getLog(DataDomainProvider.class);
 
-    private static Log logger = LogFactory.getLog(DataDomainProvider.class);
+	@Inject
+	protected ResourceLocator resourceLocator;
 
-    @Inject
-    protected ResourceLocator resourceLocator;
+	@Inject
+	protected DataChannelDescriptorMerger descriptorMerger;
 
-    @Inject
-    protected DataChannelDescriptorMerger descriptorMerger;
+	@Inject
+	protected DataChannelDescriptorLoader loader;
 
-    @Inject
-    protected DataChannelDescriptorLoader loader;
+	@Inject(Constants.SERVER_DOMAIN_FILTERS_LIST)
+	protected List<DataChannelFilter> filters;
 
-    @Inject(Constants.SERVER_DOMAIN_FILTERS_LIST)
-    protected List<DataChannelFilter> filters;
+	@Inject(Constants.SERVER_PROJECT_LOCATIONS_LIST)
+	protected List<String> locations;
 
-    @Inject(Constants.SERVER_PROJECT_LOCATIONS_LIST)
-    protected List<String> locations;
+	@Inject
+	protected Injector injector;
 
-    @Inject
-    protected Injector injector;
+	@Inject
+	protected QueryCache queryCache;
 
-    @Inject
-    protected QueryCache queryCache;
+	@Inject
+	protected RuntimeProperties runtimeProperties;
 
-    @Inject
-    protected RuntimeProperties runtimeProperties;
+	@Inject
+	protected DataNodeFactory dataNodeFactory;
 
-    @Inject
-    protected DataNodeFactory dataNodeFactory;
+	@Override
+	public DataDomain get() throws ConfigurationException {
 
-    @Override
-    public DataDomain get() throws ConfigurationException {
+		try {
+			return createAndInitDataDomain();
+		} catch (ConfigurationException e) {
+			throw e;
+		} catch (Exception e) {
+			throw new DataDomainLoadException("Error loading DataChannel: '%s'", e, e.getMessage());
+		}
+	}
 
-        try {
-            return createAndInitDataDomain();
-        } catch (ConfigurationException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new DataDomainLoadException("Error loading DataChannel: '%s'", e, e.getMessage());
-        }
-    }
+	protected DataDomain createDataDomain(String name) {
+		return new DataDomain(name);
+	}
 
-    protected DataDomain createDataDomain(String name) {
-        return new DataDomain(name);
-    }
+	protected DataDomain createAndInitDataDomain() throws Exception {
 
-    protected DataDomain createAndInitDataDomain() throws Exception {
+		DataChannelDescriptor descriptor;
 
-        DataChannelDescriptor descriptor;
+		if (locations.isEmpty()) {
+			descriptor = new DataChannelDescriptor();
+		} else {
+			descriptor = descriptorFromConfigs();
+		}
 
-        if (locations.isEmpty()) {
-            descriptor = new DataChannelDescriptor();
-            descriptor.setName(DEFAULT_NAME);
-        } else {
-            descriptor = descriptorFromConfigs();
-        }
+		String nameOverride = runtimeProperties.get(Constants.SERVER_DOMAIN_NAME_PROPERTY);
+		if (nameOverride != null) {
+			descriptor.setName(nameOverride);
+		}
 
-        DataDomain dataDomain = createDataDomain(descriptor.getName());
+		DataDomain dataDomain = createDataDomain(descriptor.getName());
 
-        dataDomain.setMaxIdQualifierSize(runtimeProperties.getInt(Constants.SERVER_MAX_ID_QUALIFIER_SIZE_PROPERTY, -1));
+		dataDomain.setMaxIdQualifierSize(runtimeProperties.getInt(Constants.SERVER_MAX_ID_QUALIFIER_SIZE_PROPERTY, -1));
 
-        dataDomain.setQueryCache(new NestedQueryCache(queryCache));
-        dataDomain.setEntitySorter(injector.getInstance(EntitySorter.class));
-        dataDomain.setEventManager(injector.getInstance(EventManager.class));
+		dataDomain.setQueryCache(new NestedQueryCache(queryCache));
+		dataDomain.setEntitySorter(injector.getInstance(EntitySorter.class));
+		dataDomain.setEventManager(injector.getInstance(EventManager.class));
 
-        dataDomain.initWithProperties(descriptor.getProperties());
+		dataDomain.initWithProperties(descriptor.getProperties());
 
-        for (DataMap dataMap : descriptor.getDataMaps()) {
-            dataDomain.addDataMap(dataMap);
-        }
+		for (DataMap dataMap : descriptor.getDataMaps()) {
+			dataDomain.addDataMap(dataMap);
+		}
 
-        dataDomain.getEntityResolver().applyDBLayerDefaults();
-        dataDomain.getEntityResolver().applyObjectLayerDefaults();
+		dataDomain.getEntityResolver().applyDBLayerDefaults();
+		dataDomain.getEntityResolver().applyObjectLayerDefaults();
 
-        for (DataNodeDescriptor nodeDescriptor : descriptor.getNodeDescriptors()) {
-            addDataNode(dataDomain, nodeDescriptor);
-        }
+		for (DataNodeDescriptor nodeDescriptor : descriptor.getNodeDescriptors()) {
+			addDataNode(dataDomain, nodeDescriptor);
+		}
 
-        // init default node
-        DataNode defaultNode = null;
+		// init default node
+		DataNode defaultNode = null;
 
-        if (descriptor.getDefaultNodeName() != null) {
-            defaultNode = dataDomain.getDataNode(descriptor.getDefaultNodeName());
-        }
+		if (descriptor.getDefaultNodeName() != null) {
+			defaultNode = dataDomain.getDataNode(descriptor.getDefaultNodeName());
+		}
 
-        if (defaultNode == null) {
-            Collection<DataNode> allNodes = dataDomain.getDataNodes();
-            if (allNodes.size() == 1) {
-                defaultNode = allNodes.iterator().next();
-            }
-        }
+		if (defaultNode == null) {
+			Collection<DataNode> allNodes = dataDomain.getDataNodes();
+			if (allNodes.size() == 1) {
+				defaultNode = allNodes.iterator().next();
+			}
+		}
 
-        if (defaultNode != null) {
-            logger.info("setting DataNode '" + defaultNode.getName() + "' as default, used by all unlinked DataMaps");
+		if (defaultNode != null) {
+			logger.info("setting DataNode '" + defaultNode.getName() + "' as default, used by all unlinked DataMaps");
 
-            dataDomain.setDefaultNode(defaultNode);
-        }
+			dataDomain.setDefaultNode(defaultNode);
+		}
 
-        for (DataChannelFilter filter : filters) {
-            dataDomain.addFilter(filter);
-        }
+		for (DataChannelFilter filter : filters) {
+			dataDomain.addFilter(filter);
+		}
 
-        return dataDomain;
-    }
+		return dataDomain;
+	}
 
-    /**
-     * @since 4.0
-     */
-    protected DataNode addDataNode(DataDomain dataDomain, DataNodeDescriptor nodeDescriptor) throws Exception {
-        DataNode dataNode = dataNodeFactory.createDataNode(nodeDescriptor);
+	/**
+	 * @since 4.0
+	 */
+	protected DataNode addDataNode(DataDomain dataDomain, DataNodeDescriptor nodeDescriptor) throws Exception {
+		DataNode dataNode = dataNodeFactory.createDataNode(nodeDescriptor);
 
-        // DataMaps
-        for (String dataMapName : nodeDescriptor.getDataMapNames()) {
-            dataNode.addDataMap(dataDomain.getDataMap(dataMapName));
-        }
+		// DataMaps
+		for (String dataMapName : nodeDescriptor.getDataMapNames()) {
+			dataNode.addDataMap(dataDomain.getDataMap(dataMapName));
+		}
 
-        dataDomain.addNode(dataNode);
-        return dataNode;
-    }
+		dataDomain.addNode(dataNode);
+		return dataNode;
+	}
 
-    private DataChannelDescriptor descriptorFromConfigs() {
+	private DataChannelDescriptor descriptorFromConfigs() {
 
-        long t0 = System.currentTimeMillis();
+		long t0 = System.currentTimeMillis();
 
-        if (logger.isDebugEnabled()) {
-            logger.debug("starting configuration loading: " + locations);
-        }
+		if (logger.isDebugEnabled()) {
+			logger.debug("starting configuration loading: " + locations);
+		}
 
-        DataChannelDescriptor[] descriptors = new DataChannelDescriptor[locations.size()];
+		DataChannelDescriptor[] descriptors = new DataChannelDescriptor[locations.size()];
 
-        for (int i = 0; i < locations.size(); i++) {
+		for (int i = 0; i < locations.size(); i++) {
 
-            String location = locations.get(i);
+			String location = locations.get(i);
 
-            Collection<Resource> configurations = resourceLocator.findResources(location);
+			Collection<Resource> configurations = resourceLocator.findResources(location);
 
-            if (configurations.isEmpty()) {
-                throw new DataDomainLoadException("Configuration resource \"%s\" is not found.", location);
-            }
+			if (configurations.isEmpty()) {
+				throw new DataDomainLoadException("Configuration resource \"%s\" is not found.", location);
+			}
 
-            Resource configurationResource = configurations.iterator().next();
+			Resource configurationResource = configurations.iterator().next();
 
-            // no support for multiple configs yet, but this is not a hard error
-            if (configurations.size() > 1) {
-                logger.info("found " + configurations.size() + " configurations for " + location
-                        + ", will use the first one: " + configurationResource.getURL());
-            }
+			// no support for multiple configs yet, but this is not a hard error
+			if (configurations.size() > 1) {
+				logger.info("found " + configurations.size() + " configurations for " + location
+						+ ", will use the first one: " + configurationResource.getURL());
+			}
 
-            ConfigurationTree<DataChannelDescriptor> tree = loader.load(configurationResource);
-            if (!tree.getLoadFailures().isEmpty()) {
-                // TODO: andrus 03/10/2010 - log the errors before throwing?
-                throw new DataDomainLoadException(tree, "Error loading DataChannelDescriptor");
-            }
+			ConfigurationTree<DataChannelDescriptor> tree = loader.load(configurationResource);
+			if (!tree.getLoadFailures().isEmpty()) {
+				// TODO: andrus 03/10/2010 - log the errors before throwing?
+				throw new DataDomainLoadException(tree, "Error loading DataChannelDescriptor");
+			}
 
-            descriptors[i] = tree.getRootNode();
-        }
+			descriptors[i] = tree.getRootNode();
+		}
 
-        long t1 = System.currentTimeMillis();
+		long t1 = System.currentTimeMillis();
 
-        if (logger.isDebugEnabled()) {
-            logger.debug("finished configuration loading in " + (t1 - t0) + " ms.");
-        }
+		if (logger.isDebugEnabled()) {
+			logger.debug("finished configuration loading in " + (t1 - t0) + " ms.");
+		}
 
-        return descriptorMerger.merge(descriptors);
-    }
+		return descriptorMerger.merge(descriptors);
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/15297cca/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder.java
index 01115b7..33dc60e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder.java
@@ -19,6 +19,7 @@
 package org.apache.cayenne.configuration.server;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -40,6 +41,12 @@ import org.apache.cayenne.di.Module;
  */
 public class ServerRuntimeBuilder {
 
+	/**
+	 * @since 4.0
+	 */
+	static final String DEFAULT_NAME = "cayenne";
+
+	private String name;
 	private Collection<String> configs;
 	private List<Module> modules;
 	private DataSourceFactory dataSourceFactory;
@@ -54,17 +61,18 @@ public class ServerRuntimeBuilder {
 	 * Creates an empty builder.
 	 */
 	public ServerRuntimeBuilder() {
-		this.configs = new LinkedHashSet<String>();
-		this.modules = new ArrayList<Module>();
+		this(null);
 	}
 
 	/**
-	 * An equivalent to creating builder with default constructor and calling
-	 * {@link #addConfig(String)}.
+	 * Creates a builder with a fixed name of the DataDomain of the resulting
+	 * ServerRuntime. Specifying explicit name is often needed for consistency
+	 * in runtimes merged from multiple configs, each having its own name.
 	 */
-	public ServerRuntimeBuilder(String configurationLocation) {
-		this();
-		addConfig(configurationLocation);
+	public ServerRuntimeBuilder(String name) {
+		this.configs = new LinkedHashSet<String>();
+		this.modules = new ArrayList<Module>();
+		this.name = name;
 	}
 
 	/**
@@ -134,6 +142,13 @@ public class ServerRuntimeBuilder {
 		return this;
 	}
 
+	public ServerRuntimeBuilder addConfigs(String... configurationLocations) {
+		if (configurationLocations != null) {
+			configs.addAll(Arrays.asList(configurationLocations));
+		}
+		return this;
+	}
+
 	public ServerRuntimeBuilder addConfigs(Collection<String> configurationLocations) {
 		configs.addAll(configurationLocations);
 		return this;
@@ -160,6 +175,28 @@ public class ServerRuntimeBuilder {
 
 	private void buildModules() {
 
+		String nameOverride = name;
+
+		if (nameOverride == null) {
+			// check if we need to force the default name ... we do when no
+			// configs or multiple configs are supplied.
+			if (configs.size() != 1) {
+				nameOverride = DEFAULT_NAME;
+			}
+		}
+
+		if (nameOverride != null) {
+
+			final String finalNameOverride = nameOverride;
+			prepend(new Module() {
+				@Override
+				public void configure(Binder binder) {
+					binder.bindMap(Constants.PROPERTIES_MAP).put(Constants.SERVER_DOMAIN_NAME_PROPERTY,
+							finalNameOverride);
+				}
+			});
+		}
+
 		if (dataSourceFactory != null) {
 
 			prepend(new Module() {
@@ -196,6 +233,7 @@ public class ServerRuntimeBuilder {
 					if (jdbcMaxConnections > 0) {
 						props.put(Constants.JDBC_MAX_CONNECTIONS_PROPERTY, Integer.toString(jdbcMaxConnections));
 					}
+
 				}
 			});
 		}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/15297cca/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/SyntheticNodeDataDomainProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/SyntheticNodeDataDomainProvider.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/SyntheticNodeDataDomainProvider.java
index 56751fe..dbd0ec2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/SyntheticNodeDataDomainProvider.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/SyntheticNodeDataDomainProvider.java
@@ -28,24 +28,23 @@ import org.apache.cayenne.configuration.DataNodeDescriptor;
  */
 class SyntheticNodeDataDomainProvider extends DataDomainProvider {
 
-    @Override
-    protected DataDomain createAndInitDataDomain() throws Exception {
+	@Override
+	protected DataDomain createAndInitDataDomain() throws Exception {
 
-        DataDomain dataDomain = super.createAndInitDataDomain();
+		DataDomain dataDomain = super.createAndInitDataDomain();
 
-        // no nodes... add a synthetic node... it will become the default
-        if (dataDomain.getDataNodes().isEmpty()) {
+		// no nodes... add a synthetic node... it will become the default
+		if (dataDomain.getDataNodes().isEmpty()) {
 
-            DataChannelDescriptor channelDescriptor = new DataChannelDescriptor();
-            channelDescriptor.setName(DEFAULT_NAME);
+			DataChannelDescriptor channelDescriptor = new DataChannelDescriptor();
 
-            DataNodeDescriptor nodeDescriptor = new DataNodeDescriptor(DEFAULT_NAME);
-            nodeDescriptor.setDataChannelDescriptor(channelDescriptor);
+			DataNodeDescriptor nodeDescriptor = new DataNodeDescriptor(ServerRuntimeBuilder.DEFAULT_NAME);
+			nodeDescriptor.setDataChannelDescriptor(channelDescriptor);
 
-            DataNode node = addDataNode(dataDomain, nodeDescriptor);
-            dataDomain.setDefaultNode(node);
-        }
-        return dataDomain;
-    }
+			DataNode node = addDataNode(dataDomain, nodeDescriptor);
+			dataDomain.setDefaultNode(node);
+		}
+		return dataDomain;
+	}
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/15297cca/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderIT.java b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderIT.java
new file mode 100644
index 0000000..27b64e4
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderIT.java
@@ -0,0 +1,109 @@
+/*****************************************************************
+ *   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.cayenne.configuration.server;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.apache.cayenne.DataRow;
+import org.apache.cayenne.conn.DataSourceInfo;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.query.SQLSelect;
+import org.apache.cayenne.test.jdbc.DBHelper;
+import org.apache.cayenne.test.jdbc.TableHelper;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class ServerRuntimeBuilderIT extends ServerCase {
+
+	@Inject
+	private DBHelper dbHelper;
+
+	@Inject
+	private ServerRuntime runtime;
+
+	@Inject
+	private DataSourceInfo dsi;
+
+	private ServerRuntime localRuntime;
+	private DataSource dataSource;
+
+	@After
+	public void stopLocalRuntime() {
+
+		// even though we don't supply real configs here, we sometimes access
+		// DataDomain, and this starts EventManager threads that need to be
+		// shutdown
+		if (localRuntime != null) {
+			localRuntime.shutdown();
+		}
+	}
+
+	@Before
+	public void testSetUp() throws Exception {
+		TableHelper tArtist = new TableHelper(dbHelper, "ARTIST");
+		tArtist.setColumns("ARTIST_ID", "ARTIST_NAME");
+		tArtist.insert(33001, "AA1");
+		tArtist.insert(33002, "AA2");
+
+		this.dataSource = runtime.getDataSource("testmap");
+	}
+
+	@Test
+	public void testConfigFree_WithDataSource() {
+
+		localRuntime = new ServerRuntimeBuilder().dataSource(dataSource).build();
+
+		List<DataRow> result = SQLSelect.dataRowQuery("SELECT * FROM ARTIST").select(localRuntime.newContext());
+		assertEquals(2, result.size());
+	}
+
+	@Test
+	public void testConfigFree_WithDBParams() {
+
+		localRuntime = new ServerRuntimeBuilder().jdbcDriver(dsi.getJdbcDriver()).url(dsi.getDataSourceUrl())
+				.password(dsi.getPassword()).user(dsi.getUserName()).minConnections(1).maxConnections(2).build();
+
+		List<DataRow> result = SQLSelect.dataRowQuery("SELECT * FROM ARTIST").select(localRuntime.newContext());
+		assertEquals(2, result.size());
+	}
+
+	@Test
+	public void test_UnnamedDomain_MultiLocation() {
+		localRuntime = new ServerRuntimeBuilder().addConfigs(CayenneProjects.TESTMAP_PROJECT,
+				CayenneProjects.EMBEDDABLE_PROJECT).build();
+
+		assertEquals("cayenne", localRuntime.getDataDomain().getName());
+	}
+
+	@Test
+	public void test_NamedDomain_MultiLocation() {
+		localRuntime = new ServerRuntimeBuilder("myd").addConfigs(CayenneProjects.TESTMAP_PROJECT,
+				CayenneProjects.EMBEDDABLE_PROJECT).build();
+		assertEquals("myd", localRuntime.getDataDomain().getName());
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/15297cca/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderTest.java b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderTest.java
index 19c9447..9177fac 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderTest.java
@@ -18,8 +18,10 @@
  ****************************************************************/
 package org.apache.cayenne.configuration.server;
 
+import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 
@@ -32,15 +34,29 @@ import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.ModuleCollection;
 import org.apache.cayenne.di.Key;
 import org.apache.cayenne.di.Module;
+import org.junit.After;
 import org.junit.Test;
 
 public class ServerRuntimeBuilderTest {
 
+	private ServerRuntime runtime;
+
+	@After
+	public void stopRuntime() {
+
+		// even though we don't supply real configs here, we sometimes access
+		// DataDomain, and this starts EventManager threads that need to be
+		// shutdown
+		if (runtime != null) {
+			runtime.shutdown();
+		}
+	}
+
 	@Test
 	public void test_NoLocation() {
 
 		// this is meaningless (no DataSource), but should work...
-		ServerRuntime runtime = new ServerRuntimeBuilder().build();
+		runtime = new ServerRuntimeBuilder().build();
 
 		List<?> locations = runtime.getInjector().getInstance(
 				Key.get(List.class, Constants.SERVER_PROJECT_LOCATIONS_LIST));
@@ -49,14 +65,14 @@ public class ServerRuntimeBuilderTest {
 		assertTrue(runtime.getModule() instanceof ModuleCollection);
 
 		Collection<Module> modules = ((ModuleCollection) runtime.getModule()).getModules();
-		assertEquals(1, modules.size());
-		assertTrue(modules.iterator().next() instanceof ServerModule);
+		assertEquals(2, modules.size());
+		assertThat(modules.iterator().next(), instanceOf(ServerModule.class));
 	}
 
 	@Test
 	public void test_SingleLocation() {
 
-		ServerRuntime runtime = new ServerRuntimeBuilder("xxxx").build();
+		runtime = new ServerRuntimeBuilder().addConfig("xxxx").build();
 
 		List<?> locations = runtime.getInjector().getInstance(
 				Key.get(List.class, Constants.SERVER_PROJECT_LOCATIONS_LIST));
@@ -65,14 +81,14 @@ public class ServerRuntimeBuilderTest {
 
 		Collection<Module> modules = ((ModuleCollection) runtime.getModule()).getModules();
 		assertEquals(1, modules.size());
-		assertTrue(modules.iterator().next() instanceof ServerModule);
+		assertThat(modules.iterator().next(), instanceOf(ServerModule.class));
 
 	}
 
 	@Test
 	public void test_MultipleLocations() {
 
-		ServerRuntime runtime = new ServerRuntimeBuilder("xxxx").addConfig("yyyy").build();
+		runtime = new ServerRuntimeBuilder().addConfigs("xxxx", "yyyy").build();
 
 		List<?> locations = runtime.getInjector().getInstance(
 				Key.get(List.class, Constants.SERVER_PROJECT_LOCATIONS_LIST));
@@ -80,8 +96,8 @@ public class ServerRuntimeBuilderTest {
 		assertEquals(Arrays.asList("xxxx", "yyyy"), locations);
 
 		Collection<Module> modules = ((ModuleCollection) runtime.getModule()).getModules();
-		assertEquals(1, modules.size());
-		assertTrue(modules.iterator().next() instanceof ServerModule);
+		assertEquals(2, modules.size());
+		assertThat(modules.iterator().next(), instanceOf(ServerModule.class));
 	}
 
 	@Test
@@ -89,15 +105,30 @@ public class ServerRuntimeBuilderTest {
 
 		Module m = mock(Module.class);
 
-		ServerRuntime runtime = new ServerRuntimeBuilder("xxxx").addModule(m).build();
+		runtime = new ServerRuntimeBuilder().addModule(m).build();
 
 		Collection<Module> modules = ((ModuleCollection) runtime.getModule()).getModules();
-		assertEquals(2, modules.size());
+		assertEquals(3, modules.size());
 
 		Iterator<Module> it = modules.iterator();
 
-		assertTrue(it.next() instanceof ServerModule);
+		assertThat(it.next(), instanceOf(ServerModule.class));
+
+		// rewind - this module is name fix module
+		it.next();
+		
 		assertSame(m, it.next());
 	}
 
+	@Test
+	public void test_UnnamedDomain_NoLocation() {
+		runtime = new ServerRuntimeBuilder().build();
+		assertEquals("cayenne", runtime.getDataDomain().getName());
+	}
+
+	@Test
+	public void test_NamedDomain_NoLocation() {
+		runtime = new ServerRuntimeBuilder("myd").build();
+		assertEquals("myd", runtime.getDataDomain().getName());
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/15297cca/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder_InAction_IT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder_InAction_IT.java b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder_InAction_IT.java
deleted file mode 100644
index 25c0aa4..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder_InAction_IT.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*****************************************************************
- *   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.cayenne.configuration.server;
-
-import org.apache.cayenne.DataRow;
-import org.apache.cayenne.conn.DataSourceInfo;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.query.SQLSelect;
-import org.apache.cayenne.test.jdbc.DBHelper;
-import org.apache.cayenne.test.jdbc.TableHelper;
-import org.apache.cayenne.unit.di.server.CayenneProjects;
-import org.apache.cayenne.unit.di.server.ServerCase;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
-import org.junit.Before;
-import org.junit.Test;
-
-import javax.sql.DataSource;
-import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-
-@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
-public class ServerRuntimeBuilder_InAction_IT extends ServerCase {
-
-    @Inject
-    private DBHelper dbHelper;
-
-    @Inject
-    private ServerRuntime runtime;
-
-    @Inject
-    private DataSourceInfo dsi;
-
-    private DataSource dataSource;
-
-    @Before
-    public void testSetUp() throws Exception { TableHelper tArtist = new TableHelper(dbHelper, "ARTIST");
-        tArtist.setColumns("ARTIST_ID", "ARTIST_NAME");
-        tArtist.insert(33001, "AA1");
-        tArtist.insert(33002, "AA2");
-
-        this.dataSource = runtime.getDataSource("testmap");
-    }
-
-    @Test
-    public void testConfigFree_WithDataSource() {
-
-        ServerRuntime localRuntime = new ServerRuntimeBuilder().dataSource(dataSource).build();
-
-        try {
-            List<DataRow> result = SQLSelect.dataRowQuery("SELECT * FROM ARTIST").select(localRuntime.newContext());
-            assertEquals(2, result.size());
-        } finally {
-            localRuntime.shutdown();
-        }
-    }
-
-    @Test
-    public void testConfigFree_WithDBParams() {
-
-        ServerRuntime localRuntime = new ServerRuntimeBuilder().jdbcDriver(dsi.getJdbcDriver())
-                .url(dsi.getDataSourceUrl()).password(dsi.getPassword()).user(dsi.getUserName()).minConnections(1)
-                .maxConnections(2).build();
-
-        try {
-            List<DataRow> result = SQLSelect.dataRowQuery("SELECT * FROM ARTIST").select(localRuntime.newContext());
-            assertEquals(2, result.size());
-        } finally {
-            localRuntime.shutdown();
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/15297cca/docs/doc/src/main/resources/RELEASE-NOTES.txt
----------------------------------------------------------------------
diff --git a/docs/doc/src/main/resources/RELEASE-NOTES.txt b/docs/doc/src/main/resources/RELEASE-NOTES.txt
index ec297f8..ddc6f5c 100644
--- a/docs/doc/src/main/resources/RELEASE-NOTES.txt
+++ b/docs/doc/src/main/resources/RELEASE-NOTES.txt
@@ -74,6 +74,7 @@ CAY-1966 SQLTemplate/SQLSelect positional parameter binding
 CAY-1967 Deprecate SQLTemplate parameter batches
 CAY-1968 SQLSelect cleanup and omissions
 CAY-1971 Variants of Property.like(..) : contains(..), startsWith(..), endsWith(..)
+CAY-1972 A property to override DataSources of multi-module projects 
 
 Bug Fixes:
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/15297cca/docs/doc/src/main/resources/UPGRADE.txt
----------------------------------------------------------------------
diff --git a/docs/doc/src/main/resources/UPGRADE.txt b/docs/doc/src/main/resources/UPGRADE.txt
index 94f8f72..c7ade60 100644
--- a/docs/doc/src/main/resources/UPGRADE.txt
+++ b/docs/doc/src/main/resources/UPGRADE.txt
@@ -35,9 +35,17 @@ UPGRADING TO 4.0.M2
 
 * External transactions are no longer configured in the Modeler. Instead they are provided as a DI property
   defined in Constants.SERVER_EXTERNAL_TX_PROPERTY.
+
 * TransactionDelegate is no longer present. Similar functionality can be achieved by writing a decorator for 
   Transaction interface and using a custom TransactionFactory to decorate standard transactions.
 
+* When switching to ServerRuntimeBuilder, users of multi-config projects may erroneously assume it has the same 
+  behavior as 3.1 ServerRuntime in assigning domain name to the resulting merged project. Which is to use the 
+  name of the last project config. We are trying to move away from this behavior, so ServerRuntimeBuilder 
+  will only use config name if there's only one config and no override. Otherwise it will use the override, 
+   or if not set - "cayenne" as the default name. Reference Jira: CAY-1972
+
+
 
 UPGRADING TO 3.1B1