Create Ticket
Warning Can't synchronize with repository "(default)" (Couldn't open Subversion repository /x1/svn/asf/bloodhound: SubversionException: ("Expected FS format between '1' and '4'; found format '6'", 160043)). Look in the Trac log for more information.
Last modified 6 months ago Last modified on Oct 28, 2013 6:25:31 PM

BEP 3 : Multi-product architecture

Title Multi-product architecture
Author The Bloodhound project
Status Final
Type Standards Track
Content-Type text/x-trac-wiki


The evolution on the field of issue tracking systems leads to many requests to support multiple products in a single environment. This document is meant to gather community consensus on the implementation of this feature . Besides it is a starting point to envision the impact on the underlying components architecture and the corresponding development strategies to get everything done. Ultimately it will explore compatibility considerations for existing plugins and suggest recommended upgrade paths so that hacks authors will take advantage of this feature .

Screenshot : BEP-0003 : Multi-product architecture


Nowadays it is possible to manage multiple projects by creating multiple environments. One notable example is the multi-environment setup considered as a reference for this specification. As a consequence data is scattered across multiple databases and maintenance tasks turn out to be more difficult. Since a long time users have expressed the need for managing multiple projects in an easy way . In the case of a family of related products it is usually important to have a unified view on the development activity as well as the ability to share common resources among them. Under those circumstances it is convenient to manage multiple products within a single Trac environment.

The term project is very generic and may be confusing considering the context. Therefore in this specification the word product is used instead .


Diagram: BEP-0003: Product environments setup

In a few words the current proposal tries to reproduce a well-known multi-environment setup inside a single enviroment. In this section you'll find the most important details necessary to implement multi-product support. In order to understand the reasons that make this a valid and reliable specification, please see Rationale below. In order to know more about similar alternatives rejected along the way, please consult Rejected ideas below.

Product environments

The key design mechanism is known as product environments . Their main goal is to provide components (both in core and those defined by plugins) with a lightweight virtual representation of an isolated environment inside the global environment when dealing with requests addressed to a resource owned by a product. The following figure illustrates how they work.

If you notice similarities with the reference multi-environment setup it is an intentional design decision. The main difference between them is that all product environments and global environment share the same database. That's the main constraint with respect to previous solutions.

Product extensions to the trac.env.Environment API

There is no need to create product environments explicitly via trac-admin commands. Provided that the target product exists in the database, they will be instantiated like shown in the following code snippet. The instance of trac.env.Environment representing the global environment will be accessed via parent attribute. The target product will be available in product attribute.

>>> env
<trac.env.Environment object at 0x7faacb1e9490>
>>> from multiproduct.env import ProductEnvironment
>>> product_env = ProductEnvironment(env, product_prefix)
>>> product_env.parent
<trac.env.Environment object at 0x7faacb1e9490>
>>> product_env.product
<multiproduct.model.Product object at 0x35566d0>

Product environments will not be recursive, which means that the following statement will fail

>>> new_product_env = ProductEnvironment(product_env, product_prefix)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Initializer must be called with 
    trac.env.Environment instance as first argument 
    (got multiproduct.env.ProductEnvironment instance instead)

Product environments are parametric singletons on the combination of environment and product prefix . In other words this means that

>>> ProductEnvironment(env, prefix) is ProductEnvironment(env, prefix)

Implementation notes

In order to make this happen and yet avoid object explosion, some caching strategy (e.g. LRU cache like functools.lru_cache or equivalent) should be installed in place, maybe combined with weak references .

Product environments will implement both trac.core.ComponentManager and trac.core.Component APIs. As a consequence instances will have their own components cache, which means that every active component class will only have a single instance for every product environment. They will inherit most of the properties of the global trac.env.Environment by acting as wrappers often forwarding method calls to be handled by the parent global environment.

The following list explains how product environments will adapt existing environment API while still being compatible with it.

  • config will contain an instance of multiproduct.config.Configuration (or equivalent) setup in such a way that it reads product-specific settings from the database . It will inherit globals settings stored in ./conf/trac.ini file and relative paths will be resolved with respect to ./conf sub-folder. Some exceptions to common behavior need to be installed in place.
    • TODO
  • shared_plugins_dir will always be empty . There will be no way to deploy plugins for a particular product, just enable/disable those installed in the global environment.
  • system_info_providers will enumerate components in the global environment implementing ISystemInfoProvider.
  • setup_participants will always be empty.
  • base_url TODO
  • base_url_for_redirect TODO
  • project_name will be the product name
  • project_description will be product description
  • project_url TODO
  • project_footer TODO
  • project_icon will be an option but will be read from product-specific configuration, and thereby it will be possible to override equivalent values configured for the global environment.
  • log_type, log_file, log_level, log_format will be options but will be read from product-specific configuration, and thereby it will be possible to override equivalent values configured for the global environment.
  • will be an option but will be read from product-specific configuration, and thereby it will be possible to override equivalent values configured for the global environment.
  • is_component_enabled will be slightly different due to environment setup constraints.
  • needs_upgrade will only return False due to environment setup constraints.
  • upgrade will only return True due to environment setup constraints.
  • get_repository TODO
  • href TODO
  • abs_href TODO
  • create will be an empty placeholder kept for the only purpose of API compatibilty.
  • shutdown will never jeopardize the availability of neither sibling product environments nor the global environment.
  • get_db_cnx, with_transaction, get_read_db, db_query, db_transaction will translate database access into product context.
  • component_activated, _component_name, db_exc, _component_rules, enable_component, and get_known_users will be verbatim copies of Trac's but will act upon product environment instance.

Remaining members of trac.env.Environment will be returned by product environments too.

Web bootstrap handlers and hooks

Web bootstrap handlers are needed to cope with flexible URL mappings. Their type contract is defined as follows:

class BootstrapHandlerBase(object):
    """Objects responsible for loading the target environment and
    request objects used in subsequent dispatching. 
    def open_environment(self, environ, start_response):
        """Load and initialize target Trac environment involved in request

        The following WSGI entries will also be present in `environ` dict:

        ||= WSGI variable =||= Environment variable =||= Comment =||
        || trac.env_path || TRAC_ENV || See wiki:TracModWSGI ||
        || trac.env_parent_dir || TRAC_ENV_PARENT_DIR || See wiki:TracModWSGI||
        || trac.env_index_template || TRAC_ENV_INDEX_TEMPLATE || See wiki:TracInterfaceCustomization ||
        || trac.template_vars || TRAC_TEMPLATE_VARS || See wiki:TracInterfaceCustomization ||
        || trac.locale ||  || Target locale ||
        || trac.base_url || TRAC_BASE_URL || Trac base URL hint ||

        This method may handle the request (e.g. render environment index page)
        in case environment lookup yields void results. In that case it MUST 
        invoke WSGI `write` callable returned by `start_response` and raise 
        `trac.web.api.RequestDone` exception.

        :param environ: WSGI environment dict
        :param start_response: WSGI callback for starting the response
        :throws RequestDone: if the request is fully processed while loading
                             target environment e.g. environment index page
        :throws EnvironmentError: if it is impossible to find a way to locate
                                  target environment e.g. TRAC_ENV and 
                                  TRAC_ENV_PARENT_DIR both missing
        :throws Exception: any other exception will be processed by the caller 
                           in order to send a generic error message back to
                           the HTTP client
        raise NotImplementedError("Must override method 'open_environment'")

    def create_request(self, env, environ, start_response):
        """Instantiate request object used in subsequent request dispatching
        :param env: target Trac environment returned by `open_environment`
        :param environ: WSGI environment dict
        :param start_response: WSGI callback for starting the response
        raise NotImplementedError("Must override method 'create_request'")


A patch has been proposed in #514 to augment the web bootstrap handlers protocol by adding probe_environment method, since it's needed to make TracStandalone authentication middlewares work under the new conditions.

Default bootstrap handler is a façade to underlying environment and request factories (i.e. hooks) which in turn by default embed product URL namespace into global namespace. Other web bootstrap handlers may be contributed by third party libraries e.g. those implementing (rejected) Routes dispatching. In order to select a given handler set either trac.bootstrap_handler WSGI variable or TRAC_BOOTSTRAP_HANDLER environment variable to an entry point expression pointing to the target instance.

Resource neighborhoods

Apache™ Bloodhound will expand resources API by introducing the concept of Neighborhoods. In a few words they are the topmost level of the hierarchy representing resources managed by a component manager, thereby identifying the later. As such resource neighborhoods serve to the purpose of specifying absolute references to resources hosted beyond the boundaries of a given component manager (e.g. Trac environments, product environments, ...) . As a side effect they will be used to load manager objects on demand.

Resource neighborhoods will always be at the top of the resources hierarchy . Instances will have a realm identifying the kind of manager (e.g. global for Trac environment, product for product environments). For example the following resource identifiers will point to attachment:file.txt:ticket:1 resource in local scope, product P1, product P2 and global environment, in that order.

+-- Resource('ticket', 1)
  +-- Resource('attachment', 'file.txt')

Neighborhood('product', 'P1')
+-- Resource('ticket', 1)
  +-- Resource('attachment', 'file.txt')

Neighborhood('product', 'P2')
+-- Resource('ticket', 1)
  +-- Resource('attachment', 'file.txt')

Neighborhood('global', None)
+-- Resource('ticket', 1)
  +-- Resource('attachment', 'file.txt')

In the previous examples all resources in Neighborhood scope will have a reference to topmost neighborhood instance in its neighbordhood attribute. If set to None resource resolution will be a responsibility of the local component manager (e.g. environment).

Loading component managers

At run time neighborhood references must be resolved to obtain the target component manager. Therefore a parallel infrastructure is needed . The new IExternalResourceManager interface is introduced for this very same purpose . Its signature is shown below :

class IExternalResourceConnector(Interface):

    def get_supported_neighborhoods():
        """Return supported manager neighborhoods.

        :rtype: `basestring` generator
    def load_manager(neighborhood):
        """Load the component manager identified by a given neighborhood.

        :param neighborhood: manager identifier (i.e. `Neighborhood`)
        :rtype: `trac.core.ComponentManager`
    def manager_exists(neighborhood):
        """Check whether the component manager associated to 
        the given `neighborhood` exists physically.

        :param neighborhood: manager identifier (i.e. `Neighborhood`)
        :rtype: bool

        Attempting to retrieve the manager object for a non-existing
        neighborhood should raise a `ResourceNotFound` exception.

Database schema changes

Products shall become a first-class citizen in Bloodhound. To make this possible, some changes will be required at the database schema level, especially as multiple product environments are meant to be hosted in the same database instance.

Currently, products are only supported in relation to tickets. Current implementation of bloodhound_multiproduct plugin extends the ticket table by adding custom field product. This field is used to reference products as defined in the bloodhound_product table.

In addition to tickets, other entities should also be made product aware. The product support will be added to the following database tables within Bloodhound:

Table Current key New key
enum type, name type, name, product
component name name, product
milestone name name, product
version name name, product
wiki name, version name, version, product

This will be accomplished using the same approach as used in relation to tickets - by extending the database tables with the 'product' field that would reference the product that the specified entities belong to.

Another change required is the change of the fore mentioned table keys. As it currently stands, the key used in these tables is limited to the 'name' field. As this (in the modified schema) prevents users from creating versions/milestones/... with the same name for different products, key should be extended with the 'product' field.

Legacy schema compatibility

As changing trac database schema (by adding product column) to the required tables will brake compatibility with existing trac code/3rd party plugins, a solution is required to solve that.

Requirements for this solution:

  • no code changes are required within trac/3rd party plugins to operate within this new schema/environment
  • trac/3rd party plugins should see a view of the database depending on what product scope is currently active

As existing trac/3rd party plugin code should remain unchanged, the only way of accomplishing legacy compatibility with the new database schema is to develop a component, that installs itself directly above the database access layer. By intercepting SQL statements and transforming them, trac/3rd party plugins are given a view of the database that corresponds to the currently active product scope.

Currently all database access is handled by trac class IterableCursor. A new class is implemented called BloodhoundIterableCursor that replaces the IterableCursor in runtime.

Functionality of this class is the following:

  • parse SQLs targeted at the database
  • depending to which table the SQL is targeted, the following transformation is done on the SQL
    • for system tables or tables that do not require productization, SQL is passed unchanged
    • for 'productized' trac tables mentioned above, a product view of the table is presented to the code
      • only DML statements (SELECT, INSERT, UPDATE, DELETE) are translated
    • queries targeted at 3rd party plugin tables are modified to prefix 3rd party plugin table names with product prefix
      • in addition to DML, DDL statements (CREATE TABLE/INDEX, ALTER TABLE/CONSTRAINT, DROP TABLE) are also translated

Examples of translated SQLs are available at legacy schema compatibility.

Administration commands

The following new administration commands will be implemented

Command Parameters Description
deploy-multiproduct TODO In a way similar to built-in deploy command it will extract environment assets and create scripts for default deployment via CGI, FastCGI or mod_wsgi.

Product file system hierarchy

The subfolder ./products/<product prefix> relative to the top-level directory of the global environment will contain the product file system area. Attachments for resources owned by a product will be stored in there . Moreover plugins (e.g. trachacks:GraphvizPlugin) may use it to store files as they did in single environment deployments .

Implementation notes

The code of Trac components structures file-system access using paths relative to the environment's top-level folder . Considering the fact that plugins migration towards multi-product support should be as smooth as possible the layout of product file-system area will be exactly the same as before . In other words, if the global environment's root folder is /path/to/env then the root folder of file-system area for product (prefix) P will be /path/to/env/products/P . Attachment foo.txt for ticket 42 in product P will be located at /path/to/env/products/P/files/attachments/ticket/92c/92cfceb39d57d914ed8b14d0e37643de0797ae56/9206ac42b532ef8e983470c251f4e1a365fd636c.txt whereas attachment foo.txt for ticket 42 in the global environment will be located at /path/to/env/files/attachments/ticket/92c/92cfceb39d57d914ed8b14d0e37643de0797ae56/9206ac42b532ef8e983470c251f4e1a365fd636c.txt.

Product-aware components may do a different thing if they need to .


The following is a list of proposed features for multi product support in Bloodhound. Goals related to compatibility are considered in Backwards compatibility below. In each case you'll find notes explaining how candidate implementation will solve related issues.

Product components ecosystem

All the functionalities installed in the global environment (e.g. blogs, pastebins) should be available for products as well. They may be enabled/disabled on a per product basis.

Implementation notes

Product environments are meant to implement trac.core.ComponentManager API and inherit global plugins installations by design. Hence every component class will have a singleton instance for each product (besides the one for the global environment). Components are enabled/disabled via [components] section in configuration file. The fact that each product will have a separate configuration file also means that there will be one such section to enable/disable each class on a per product basis.

#components-env-setup This situation is quite similar to what happens nowadays in multi-environment installations. The main difference will be that a component may be enabled for a given product only if it's been previously enabled for the global environment . This constraint is aimed at scoping environment setup tasks (e.g. database updgrades) in the context of the global environment. Product environments will not participate at all in this process so as to avoid some undesired side-effects . One of them is explained below .

Let's suppose plugins may be enabled at will in both global environment and product environments. Initially environment state is steady . Component C is installed but it has never been enabled . It requires a database upgrade . Suddenly it's enabled for product P1 . As a consequence environment upgrade should be applied (new tables, etc ...) . Such upgrade will only be possible if an instance of C announces that such changes must be carried out. However there will be no such setup participant neither in the global environment list nor any other product environment besides P1 . Summarizing, allowing for such scattered differences may lead to

  1. Inconsistences across products requiring different upgrades but sharing the same underlying database .
  2. Complicated administration commands and related architecture .
  3. Annoying 'needs upgrade' messages showing any time and degrading user experience .
  4. Unnecessary administration overhead .

Product-specific settings

Component settings should be configurable per product. For practical reasons if there is no explicit option assignment set for a particular product then it should inherit option value set for the global environment.

Implementation notes

Product-specific settings will be implemented in #115 .

trac.conf.Configuration instance in config attribute of product environments will read settings from the database and will be configured to inherit settings in environments' trac.ini file.

Even if the proposal only suggests one database backend to store product-specific configuration it is also possible to think of using other configuration repositories (e.g. ini files, HDFS, remote data repositories). It is a feasible enhancement though details have been omitted.

The following requirements are a corollary, considering that such customizations are performed in the configuration file.

On accessing resources owned by a product its logo and description will be shown.

Implementation notes

Edit [header-logo] section in product settings table.

Per product ticket workflow

Depending on the product, different ticket workflows should be supported.

Implementation notes

Edit [ticket-workflow] section in product settings table.

Advanced ticket workflow customization will be possible for individual products since components may be enabled/disabled on a per product basis.

Per product notifications

Notifications should be configurable per product.

Implementation notes

Edit [notification] section in product settings table.

Per product ticket field configuration

Components, milestone, version, priority, defaults, custom fields should be configurable per product.

Implementation notes

Edit [ticket-custom] section in product settings table.

Per product permission scheme

Permission scheme is defined by assigning permissions (from a predefined permission list) to specific users or groups. Permission scheme is assigned to a product.

Product roles

Support for per product user groups. Roles can be used to configure notifications and permissions per product.

Product resources namespaces

Product and resource ID should form a two dimensional namespace. The mapping should be flexible enough to support the scenarios mentioned below. Other deployments may still be possible but are beyond the scope of this specification. However, in all cases every requests sent to any path under product base URL will be handled by the request handlers and filters activated in the target product environment.

  • Product sub domain : The base URL of products will be at sibling sub-domains. A well-known example is (i.e.,, ). The global environment may be accessible at one such sub-domain, at top level domain, or somewhere else.
  • Product path namespace : Each product is accessible at sibling paths on the same domain. The global environment will be available either at one such path or somewhere else.
  • Embedded product path namespace : Each product is accessible at sibling paths inside the URL space of the global environment.
  • Product sub domain + path namespace : In this case product prefix will be matched against URL sub domain whereas Bloodhound will be accessed at a given path inside of it. This case is frequently offered by hosting providers like forges deploying multiple web applications for projects . In general product base URL will look like http://<product prefix>.domain.tld/path/to/bloodhound .
  • Arbitrary product path namespace : Sometimes hosting providers like forges do not support product sub-domains . Hence product prefix is included in URL path. In general, product base URLs will look like this http://domain.tld/path/to/<product prefix>/path/to/bloodhound . One such hypothetical example would be<product prefix>/bloodhound .

FIXME also be addressable through the product URL namespace, namely /ticket/<product prefix>/<local ticket id>.

In a multi-product configuration product resources should not be accessed using current global URL scheme (i.e. /path/to/bloodhound/<environment>/<realm>/<id>). Since products will have their own permissions schema then requests handled by components in the context of the top-level environment will perform neither the right permissions checks nor even use appropriate settings, and so on ... The same will happen for resources moved between products . In general such requests should be redirected to a URL under the namespace of resource's product.


Tickets will keep their absolute ID and will be unique in the context of the global environment. Besides each product would have a separate number sequence for product ticket IDs.

TODO: Implementation details TBD

Ubiquitous access to resources

Existing resource APIs have to be extended to work across product boundaries (or component manager, if the feature is analysed from a more abstract perspective) . While performing some computation (e.g. request handling, trac-admin command, ...) it will be possible to refer to resources managed by another product (i.e. in another scope) . Notable use cases are:

  • Rendering context
  • Permission checks and related assertions
  • Link generation in source code and Genshi templates
  • Resource management

Integrating resources managed by other applications

Interoperability with resources managed by other applications has been requested before . IExternalResourceConnector and Neighborhoods will provide the means to make that happen. For instance, the following resource identifier could be used to refer to this JIRA ticket

Neighborhood('jira', '')
  +-- Resource('project', 'COMDEV')
    +-- Resource('ticket', 84)

Resources moveable between products

Tickets should be moveable between products, old ticket product IDs (and URLs) should be remembered, making the same ticket accessible through old products namespaces (URLs).

By default, search is global. Search and queries should allow search queries to be limited to specific product.

Inter product ticket relations

It should be possible to link tickets from different products.

Per product repository

Each product can have different repository (and type) assigned.

Ticket updates on commit

The multi-product architecture documented in this proposal requires introducing disambiguation in CommitTicketUpdater component responsible for binding tickets to changesets. The fact that the same ticket IDs may be repeated in different product contexts together with many-to-many relationships between products and repositories make the former ticket syntax unsuitable under the new conditions. The following modifications will be applied upon existing code.

  • Supported ticket references will be PREFIX-1 , product:PREFIX:ticket:1 , PREFIX->ticket:1
    • This change is aimed at being consistent with product-aware ticket wiki syntax, especially when rendering commit messages in the repository browser
  • The : character may not be omitted but on the other hand bug and issue may still be used as a replacement for ticket
  • A new option check_links (similar to check_perms) will be added so as to limit updates to tickets in products linked to target repository
    • Should a mismatch be detected the event will be reported using the logging handler bound to the global environment

TODO Integration with ticket workflow

TODO How to render in the repository browser previous local ticket references (e.g. #3) for repositories linked to multiple products ?

Data migration

The final solution will be prepared to import data from other issue tracker systems and still provide a similar user experience. Important examples to consider are :

Product attachments

Each product will have a separate set of attachments . This means that ticket 1 in product P1 and ticket 1 in product P2 may have attachments with the same name but different contents .

Other minor features

System information has to be consistent across product environments .

Rejected ideas

Many interesting ideas have been proposed but not all could make their way into final specification because of conceptual, practical or other reasons. Below you'll find the most relevant instances together with brief comments explaining the decision as well as links to relevant messages in bloodhound-dev mailing list archive .

Disabled State for Multiproduct Components API (rejected)

It was suggested that instantiating a component involved in multi-product architecture should always return None, just like if it was disabled e.g.

>>> from multiproduct.api import MultiProductSystem
>>> ps = product_env[MultiProductSystem]
>>> repr(ps)

This is not recommended. Multi-product API components will play an active role in customizations at the product level. That will not be possible if they are disabled.

Rejected alternatives to resource neighborhoods

Among many solutions considered for resources API it's worth to mention some candidates and the reasons for not adopting them.

Extend the signature of resource-aware methods

As an alternative to resource neighborhoods the signature of resource aware methods could have been extended with a parameter (e.g. compmgr) accepting an instance of trac.core.ComponentManager . Its default value would be set to None for local resources. This approach has the following limitations :

  • It has an impact on code scattered all over Trac core and plugins
  • Even if all invocations performed by resource API clients will still work for local resources, they'll have to be modified to cope with external references.
    • ... which represents a challenge when it comes to providing generic helpers to render links in Genshi templates .

Resources hierarchy

Another promising approach was to represent absolute references to product resources by inserting a product resource (i.e. realm = 'product' as the topmost element in the hierarchy . Sample ticket and attachment references are shown below :

Resource('product', 'PREFIX')
+-- Resource('ticket', 1)

Resource('product', 'PREFIX')
+-- Resource('ticket', 1)
  +-- Resource('attachment', 'file.txt')

The drawbacks in this case are :

  • Trac resources API at present is focused on resource realms and does not provide extension points to perform this kind of generic resource management operations.
    • It's difficult to intercept resource management extensions without patching Trac core in many locations.
  • There will be collisions with existing resource API classes
    • 'product' is a resource realm too .
  • Components making use of resource API classes will have to be updated as well by dealing with product resources in special ways
    • The core will be tightly coupled to the product concept.
    • The solution does not scale to other kinds of component managers
    • It is slightly complicated to implement because, among other things, it is hard to determine the exact point to instantiate component managers.
  • It would be necessary to traverse the resources hierarchy and «know-what-to-do» (i.e. what realm has to be used to instantiate a given component manager sub-type) to determine product context.
    • Subtle impact on performance
  • There is no space for managers that are not bound to resources e.g. trac.env.Environment
    • No support in product environments for links to resources in global environment ... unless relying upon yet another hack.
  • Wrong resource will be referenced by non-product aware components. In other words, if a plugin makes use of resources and it is not aware of the new hierarchy hack then the following references will be both resolved to the local attachment:file.txt:ticket:1. That's wrong.
Resource('product', 'PREFIX')
+-- Resource('ticket', 1)
  +-- Resource('attachment', 'file.txt')

Resource('ticket', 1)
+-- Resource('attachment', 'file.txt')

Revert req.product_perm

Along the way bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/ added product_perm method in request objects to instantiate permission cache objects in a given product context. This change clashes with Trac core and leads to compatibility issues . This method will not be necessary any more considering the fact that the same result will be achieved by executing the following statement.

product_perm = req.perm(Neighborhood('product', 'PREFIX'))

Product routes

Former versions stated that Routes framework would be used to match URLs based on predefined patterns and use this information to dispatch requests addressed to product environments. This proposal was rejected. Routes web bootstrap handlers will not be distributed with mainstream Apache™ Bloodhound package due to the fact that this will introduce an explicit installation dependency. Nevertheless related details are still documented in routes appendix for historical reasons and also because this approach will be provided by third-party libraries.

Backwards Compatibility

The solution has to be compatible with single product solution whenever possible in order to make possible smooth upgrade paths from previous installations. This is particularly important for plugins to work out-of-the-box under the new circumstances or at least to make easier the upgrade development process for hack authors.

Product environments in proposed multi-product configuration will act as the replacement for sibling environments under common parent directory in reference multi-environments setup. They will ensure that the correct translations will be performed for requests addressed to resources owned by a product using the trac.env.Environment API as usual. Components will have access to it by reading self.env attribute (as usual !!!). All this means that from a component perspective the very same operations will be invoked, thus plugins adaptation to the new conditions will be rather smooth. If plugins do want to know whether they are acting on behalf of a product environment or the global environment then the following conditional statement would be enough

from trac.env import Environment
from multiproduct.env import ProductEnvironment

if isinstance(self.env, Environment):
    # Trac environment
elif isinstance(self.env, ProductEnvironment):
    # Product environment ... activate super-powers !
    # Unidentified environment object ... aliens & zombies !

Instances of Neighborhood class will satisfy the contract of Resource class. Some parameters (e.g. version and parent) will be ignored when appropriate. Therefore uniform access to both instances will be offered. That means that existing code will not raise any type error if an instance of Resource is substituted by an instance of Neighborhood class.

Reference Implementation

Finalisation notice

The multi-product architecture described in this document has been committed into project /trunk and is distributed to users since version 0.6.0 .

Multi-product plugin is under active development by the Bloodhound community. It is possible to check out the source using Apache™ Subversion by executing the following command

$ svn co

BEP 3 ticket summary

(24 total rows)
Ticket Summary Status Priority Milestone
#434 Ticket query macro in product context - after #390 closed blocker Release 6
#438 Implement and enforce product permission policy closed critical Release 6
#441 Put product request handlers in the right context closed critical Release 6
#115 Product-specific settings closed major Release 6
#288 Create prototype for legacy database schema proxy closed major Release 6
#322 Add support for environment factory configurable through trac.ini closed major Release 6
#323 Add support for custom hooks configurable through trac.ini closed major Release 6
#350 Enable / disable components for a particular product. closed major Release 6
#356 Update installer to enable product environment factory closed major Release 6
#357 Implement product environment factory closed major Release 6
#386 Test product environment's href resolution closed major Release 6
#402 Add default product permissions on install closed major Release 6
#404 Populate default schema on product addition closed major Release 6
#406 Database upgrade to multiproduct closed major Release 6
#407 Multiproduct setup fails when adding TRAC_ADMIN permissions closed major Release 6
#430 Multiproduct UI: Administration pages closed major Release 6
#440 Implement ProductEnvironments as parametric singletons closed major Release 6
#470 Multiproduct version control repository support closed major Release 6
#475 Console admin commands (trac-admin) for products closed major Release 6
#492 Multi-product resource hierarchy closed major Release 6
#495 Permission schemes closed major
#514 TracStandalone middlewares compatible with hooks closed major Release 6
#593 Upgrade i.a.o/bh to multi-product bep:0003 new major
#676 Relax constraints on ProductEnvironment callable contract closed major Release 8


Source code management is powered by Apache™ Subversion.

The mind map has been created with Mindjet for Android mobile client. On PC and Mac systems you can view its contents using FreeMind desktop client.


  1. Multi-project support
  2. Multiple Projects within a Single Trac Environment
  3. Setuptools dynamic discovery of services and plugins

Copyright © 2009-2012 The Apache Software Foundation
Licensed under the Apache License, Version 2.0.

Apache Bloodhound, Apache, the Apache feather logo, and the Apache Bloodhound project logo are trademarks of The Apache Software Foundation.


Download all attachments as: .zip