Return-Path: Delivered-To: apmail-geronimo-dev-archive@www.apache.org Received: (qmail 4440 invoked from network); 27 Jun 2005 14:36:14 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 27 Jun 2005 14:36:14 -0000 Received: (qmail 71033 invoked by uid 500); 27 Jun 2005 14:36:05 -0000 Delivered-To: apmail-geronimo-dev-archive@geronimo.apache.org Received: (qmail 70961 invoked by uid 500); 27 Jun 2005 14:36:04 -0000 Mailing-List: contact dev-help@geronimo.apache.org; run by ezmlm Precedence: bulk list-help: list-unsubscribe: List-Post: Reply-To: dev@geronimo.apache.org List-Id: Delivered-To: mailing list dev@geronimo.apache.org Delivered-To: moderator for dev@geronimo.apache.org Received: (qmail 75395 invoked by uid 99); 27 Jun 2005 06:03:59 -0000 X-ASF-Spam-Status: No, hits=0.0 required=10.0 tests= X-Spam-Check-By: apache.org Received-SPF: neutral (asf.osuosl.org: local policy) Message-ID: <42BF96D1.3020107@senselogic.se> Date: Mon, 27 Jun 2005 08:04:01 +0200 From: =?ISO-8859-1?Q?Rickard_=D6berg?= User-Agent: Mozilla Thunderbird 1.0.2 (Windows/20050317) X-Accept-Language: en-us, en MIME-Version: 1.0 To: dev@picocontainer.codehaus.org CC: dev@geronimo.apache.org Subject: Re: [picocontainer-dev] Re: ClassLoader Architecture References: <8C051B4D-95E3-4876-8941-1D9855BB8003@iq80.com> <6E8E8A09-5D16-4A5E-999A-52AD75888F7D@apache.org> <42BEC95F.1000208@senselogic.se> <42BF0123.9030506@apache.org> In-Reply-To: <42BF0123.9030506@apache.org> Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N Jeremy Boynes wrote: > I would be very interested if you have the time. Since you asked :-) There are a couple of issues I wanted to fix with such a scheme. First of all, a component should be able to expose an API, and only that API should be exposed by the component. Other components should not be able to access helper objects or components, and they should not be able to cast the component to other interfaces than what is explicitly exposed. Second, a component must be able to either use other API's exposed by other components (trivial) *or* have an internal structure where it hosts components as helpers, and which are not exposed to other components. An example of the latter might be a DataSource that is specific for a component and which should not be autowired into any other component. A component should not "leak" by accident. Third, components must be lazy-loadable, yet eager-resolvable. Our system is so large that starting up all components at startup-time takes way too long. Components (or at least some) should be lazy-loadable(/lazy-startable) upon the first call to any of the methods in the exposed API. The basic approach is to use ContainerComposers (CC), which (IIRC) is how MCA works as well. One component, one CC. I then have a base class that allows me to do things like this: public class PortletContainerComponent extends ComponentContainerComposer { public void composeContainer(MutablePicoContainer parent, Object o) { MutablePicoContainer container = makeChildContainer(parent); container.registerComponentImplementation(PortletRegistryImpl.class); container.registerComponentImplementation(PortletDeployer.class); container.registerComponentImplementation(TomcatJMXPortletDeployer.class); container.registerComponentImplementation(PortletRenderer.class); register(PortletRegistry.class); register(PortletRenderer.class); } } --- A component using this approach always does three things: 1) set up a child container for the internal structure. Using makeChildContainer() on the provided parent is the most common, but it can be more complex if you want to. 2) configure the internal structure 3) expose the API, which will register the API in "parent" Requirement 1 and 3 above makes it necessary to introduce proxies which can be eagerly resolved by other components without having to have an actual backend component at the resolution time. This, of course, also allows components to be passivated at any time if necessary, but so far the lazy-start is the most important to me. By using the implementation hiding component adapter it is also ensured that only the API is exposed. For example, if the exposed class PortletRegistryImpl implements Startable this is not visible to "parent" above. Hence lifecycle events will only be executed in the internal container. Creating and registering the proxy is done in step 3) above. To allow for explicit lazy-loading of components I can do things like this: public class SearchEngineComponent extends ComponentContainerComposer { public void composeContainer(MutablePicoContainer con, Object o) { MutablePicoContainer container = makeChildContainer(con, LAZY_LOAD); container.registerComponentImplementation(SearchEngineImpl.class); container.registerComponentImplementation(ClientSearchEngineImpl.class); register(SearchEngine.class); register(SearchIndex.class); register(ClientSearch.class); } } --- This will ensure that the objects in this component will only be instantiated and started iff a method of the exposed API is called. Lazy-loading can only be done if none of the objects start threads or exposes some static methods that are not exposed through a Pico API interface. For example, the above would be only half of the search component in our system, with the indexing (having threads) added like so: public class IndexingComponent extends ComponentContainerComposer { public void composeContainer(MutablePicoContainer con, Object o) { MutablePicoContainer container = makeChildContainer(con); container.registerComponentImplementation(SearchIndexOptimizer.class); container.registerComponentImplementation(IndexMaintainer.class); container.registerComponentImplementation(IndexMaintainListener.class); container.registerComponentImplementation(IndexRebuilder.class); register(IndexRebuilder.class); register(IndexMaintainer.class); } } --- ... since IndexMaintainer uses threads and hence needs to be started before anyone calls its API. These two together implement the "search component" in our system: public class SearchComponent extends ComponentContainerComposer { public void composeContainer(MutablePicoContainer con, Object o) { new IndexingComponent().composeContainer(con, o); new SearchEngineComponent().composeContainer(con, o); } } --- Note that in this case the composer does not create a sub-container, but instead composes itself directly using the other composers. If it had created a new child container it would have to explicitly register() the API of the child components in order to "push" them upwards. This explicitness ensures that it is impossible to accidentally access the internal structure of a component. That's about it I think. Some implementation details in ComponentContainerComposer, and there's no class loading going on above (would be easy to add it though). Overall, this scheme should make it possible to have lots of components, large components, composed components, expose what you want, hide what you want, and yet be reasonably easy to understand and use. Comments, thoughts, suggestions? /Rickard