001// Copyright 2006, 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007// http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.ioc;
016
017import org.apache.tapestry5.ioc.annotations.SubModule;
018import org.apache.tapestry5.ioc.def.ModuleDef;
019import org.apache.tapestry5.ioc.internal.DefaultModuleDefImpl;
020import org.apache.tapestry5.ioc.internal.LoggerSourceImpl;
021import org.apache.tapestry5.ioc.internal.RegistryImpl;
022import org.apache.tapestry5.ioc.internal.RegistryWrapper;
023import org.apache.tapestry5.ioc.internal.services.ClassFactoryImpl;
024import org.apache.tapestry5.ioc.internal.services.PlasticProxyFactoryImpl;
025import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
026import org.apache.tapestry5.ioc.internal.util.InternalUtils;
027import org.apache.tapestry5.ioc.internal.util.OneShotLock;
028import org.apache.tapestry5.ioc.services.ClassFactory;
029import org.apache.tapestry5.ioc.services.PlasticProxyFactory;
030import org.apache.tapestry5.ioc.services.TapestryIOCModule;
031import org.slf4j.Logger;
032
033import java.lang.reflect.AnnotatedElement;
034import java.util.Arrays;
035import java.util.List;
036import java.util.Set;
037
038/**
039 * Used to construct the IoC {@link org.apache.tapestry5.ioc.Registry}. This class is <em>not</em> thread-safe. The
040 * Registry, once created, <em>is</em> thread-safe.
041 */
042public final class RegistryBuilder
043{
044    private final OneShotLock lock = new OneShotLock();
045
046    /**
047     * Module defs, keyed on module id.
048     */
049    final List<ModuleDef> modules = CollectionFactory.newList();
050
051    private final ClassLoader classLoader;
052
053    private final Logger logger;
054
055    private final LoggerSource loggerSource;
056
057    private final ClassFactory classFactory;
058
059    private final PlasticProxyFactory proxyFactory;
060
061    private final Set<Class> addedModuleClasses = CollectionFactory.newSet();
062
063    public RegistryBuilder()
064    {
065        this(Thread.currentThread().getContextClassLoader());
066    }
067
068    public RegistryBuilder(ClassLoader classLoader)
069    {
070        this(classLoader, new LoggerSourceImpl());
071    }
072
073    public RegistryBuilder(ClassLoader classLoader, LoggerSource loggerSource)
074    {
075        this.classLoader = classLoader;
076        this.loggerSource = loggerSource;
077        logger = loggerSource.getLogger(RegistryBuilder.class);
078
079        // Make the ClassFactory appear to be a service inside TapestryIOCModule, even before that
080        // module exists.
081
082        Logger classFactoryLogger = loggerSource.getLogger(TapestryIOCModule.class.getName() + ".ClassFactory");
083        Logger proxyFactoryLogger = loggerSource.getLogger(TapestryIOCModule.class.getName() + ".PlasticProxyFactory");
084
085        classFactory = new ClassFactoryImpl(this.classLoader, classFactoryLogger);
086        proxyFactory = new PlasticProxyFactoryImpl(this.classLoader, proxyFactoryLogger);
087
088        add(TapestryIOCModule.class);
089    }
090
091    /**
092     * Adds a {@link ModuleDef} to the registry, returning the builder for further configuration.
093     */
094    public RegistryBuilder add(ModuleDef moduleDef)
095    {
096        lock.check();
097
098        // TODO: Some way to ensure that duplicate modules are not being added.
099        // Part of TAPESTRY-2117 is in add(Class...) and that may be as much as we can
100        // do as there is no concept of ModuleDef identity.
101
102        modules.add(moduleDef);
103
104        return this;
105    }
106
107    /**
108     * Adds a number of modules (as module classes) to the registry, returning the builder for further configuration.
109     *
110     * @see org.apache.tapestry5.ioc.annotations.SubModule
111     */
112    public RegistryBuilder add(Class... moduleClasses)
113    {
114        lock.check();
115
116        List<Class> queue = CollectionFactory.newList(Arrays.asList(moduleClasses));
117
118        while (!queue.isEmpty())
119        {
120            Class c = queue.remove(0);
121
122            // Quietly ignore previously added classes.
123
124            if (addedModuleClasses.contains(c))
125                continue;
126
127            addedModuleClasses.add(c);
128
129            logger.info("Adding module definition for " + c);
130
131            ModuleDef def = new DefaultModuleDefImpl(c, logger, proxyFactory);
132            add(def);
133
134            SubModule annotation = ((AnnotatedElement) c).getAnnotation(SubModule.class);
135
136            if (annotation == null)
137                continue;
138
139            queue.addAll(Arrays.asList(annotation.value()));
140        }
141
142        return this;
143    }
144
145    /**
146     * Adds a modle class (specified by fully qualified class name) to the registry, returning the builder
147     * for further configuration.
148     *
149     * @see org.apache.tapestry5.ioc.annotations.SubModule
150     */
151    public RegistryBuilder add(String classname)
152    {
153        lock.check();
154
155        try
156        {
157            Class builderClass = Class.forName(classname, true, classLoader);
158
159            add(builderClass);
160        } catch (Exception ex)
161        {
162            throw new RuntimeException(String.format("Failure loading Tapestry IoC module class %s: %s", classname,
163                    InternalUtils.toMessage(ex), ex));
164        }
165
166        return this;
167    }
168
169    /**
170     * Constructs and returns the registry; this may only be done once. The caller is responsible for invoking
171     * {@link org.apache.tapestry5.ioc.Registry#performRegistryStartup()}.
172     */
173    public Registry build()
174    {
175        lock.lock();
176
177        RegistryImpl registry = new RegistryImpl(modules, classFactory, proxyFactory, loggerSource);
178
179        return new RegistryWrapper(registry);
180    }
181
182    public ClassLoader getClassLoader()
183    {
184        return classLoader;
185    }
186
187    public Logger getLogger()
188    {
189        return logger;
190    }
191
192    /**
193     * Constructs the registry, adds a {@link ModuleDef} and a number of modules (as module classes) to the registry and
194     * performs registry startup. The returned registry is ready to use. The caller is must not invoke
195     * {@link org.apache.tapestry5.ioc.Registry#performRegistryStartup()}.
196     *
197     * @param moduleDef     {@link ModuleDef} to add
198     * @param moduleClasses modules (as module classes) to add
199     * @return {@link Registry}
200     * @since 5.2.0
201     */
202    public static Registry buildAndStartupRegistry(ModuleDef moduleDef, Class... moduleClasses)
203    {
204        RegistryBuilder builder = new RegistryBuilder();
205
206        if (moduleDef != null)
207            builder.add(moduleDef);
208
209        builder.add(moduleClasses);
210
211        Registry registry = builder.build();
212
213        registry.performRegistryStartup();
214
215        return registry;
216    }
217
218    /**
219     * Constructs the registry, adds a number of modules (as module classes) to the registry and
220     * performs registry startup. The returned registry is ready to use. The caller is must not invoke
221     * {@link org.apache.tapestry5.ioc.Registry#performRegistryStartup()}.
222     *
223     * @param moduleClasses modules (as module classes) to add
224     * @return {@link Registry}
225     * @since 5.2.0
226     */
227    public static Registry buildAndStartupRegistry(Class... moduleClasses)
228    {
229        return buildAndStartupRegistry(null, moduleClasses);
230    }
231}