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