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