001package org.apache.turbine.services.assemblerbroker;
002
003
004/*
005 * Licensed to the Apache Software Foundation (ASF) under one
006 * or more contributor license agreements.  See the NOTICE file
007 * distributed with this work for additional information
008 * regarding copyright ownership.  The ASF licenses this file
009 * to you under the Apache License, Version 2.0 (the
010 * "License"); you may not use this file except in compliance
011 * with the License.  You may obtain a copy of the License at
012 *
013 *   http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing,
016 * software distributed under the License is distributed on an
017 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
018 * KIND, either express or implied.  See the License for the
019 * specific language governing permissions and limitations
020 * under the License.
021 */
022
023
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.Iterator;
027import java.util.List;
028import java.util.Map;
029
030import org.apache.commons.collections.map.LRUMap;
031import org.apache.commons.configuration.Configuration;
032import org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034import org.apache.turbine.Turbine;
035import org.apache.turbine.TurbineConstants;
036import org.apache.turbine.annotation.AnnotationProcessor;
037import org.apache.turbine.modules.Assembler;
038import org.apache.turbine.modules.Loader;
039import org.apache.turbine.services.InitializationException;
040import org.apache.turbine.services.TurbineBaseService;
041import org.apache.turbine.services.assemblerbroker.util.AssemblerFactory;
042import org.apache.turbine.util.TurbineException;
043
044/**
045 * TurbineAssemblerBrokerService allows assemblers (like screens,
046 * actions and layouts) to be loaded from one or more AssemblerFactory
047 * classes.  AssemblerFactory classes are registered with this broker
048 * by adding them to the TurbineResources.properties file.
049 *
050 * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
051 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
052 * @version $Id: TurbineAssemblerBrokerService.java 1521999 2013-09-11 19:26:21Z tv $
053 */
054public class TurbineAssemblerBrokerService
055        extends TurbineBaseService
056        implements AssemblerBrokerService
057{
058    /** Logging */
059    private static Log log
060            = LogFactory.getLog(TurbineAssemblerBrokerService.class);
061
062    /** A structure that holds the registered AssemblerFactories */
063    private Map<Class<?>, List<?>> factories = null;
064
065    /** A cache that holds the generated Assemblers */
066    private Map<String, Assembler> assemblerCache = null;
067
068    /** A cache that holds the Loaders */
069    private Map<Class<?>, Loader<? extends Assembler>> loaderCache = null;
070
071    /** Caching on/off */
072    private boolean isCaching;
073
074    /**
075     * Get a list of AssemblerFactories of a certain type
076     *
077     * @param type type of Assembler
078     * @return list of AssemblerFactories
079     */
080    @SuppressWarnings("unchecked")
081    private <T extends Assembler> List<AssemblerFactory<T>> getFactoryGroup(Class<T> type)
082    {
083        if (!factories.containsKey(type))
084        {
085            factories.put(type, new ArrayList<AssemblerFactory<T>>());
086        }
087        return (List<AssemblerFactory<T>>) factories.get(type);
088    }
089
090    /**
091     * Utility method to register all factories for a given type.
092     *
093     * @param type type of Assembler
094     * @throws TurbineException
095     */
096    private void registerFactories(String type)
097        throws TurbineException
098    {
099        List<Object> names = getConfiguration().getList(type);
100
101        log.info("Registering " + names.size() + " " + type + " factories.");
102
103        for (Iterator<Object> it = names.iterator(); it.hasNext(); )
104        {
105            String factory = (String) it.next();
106            try
107            {
108                @SuppressWarnings("unchecked")
109                AssemblerFactory<? extends Assembler> af =
110                    (AssemblerFactory<? extends Assembler>) Class.forName(factory).newInstance();
111                registerFactory(af);
112            }
113            // these must be passed to the VM
114            catch (ThreadDeath e)
115            {
116                throw e;
117            }
118            catch (OutOfMemoryError e)
119            {
120                throw e;
121            }
122            // when using Class.forName(), NoClassDefFoundErrors are likely
123            // to happen (missing jar files)
124            catch (Throwable t)
125            {
126                throw new TurbineException("Failed registering " + type
127                        + " factory: " + factory, t);
128            }
129        }
130    }
131
132    /**
133     * Initializes the AssemblerBroker and loads the AssemblerFactory
134     * classes registered in TurbineResources.Properties.
135     *
136     * @throws InitializationException
137     */
138    @SuppressWarnings("unchecked") // as long as commons-collections does not use generics
139    @Override
140    public void init()
141        throws InitializationException
142    {
143        factories = new HashMap<Class<?>, List<?>>();
144
145        try
146        {
147            Configuration conf = getConfiguration();
148
149            for (Iterator<String> i = conf.getKeys(); i.hasNext();)
150            {
151                String type = i.next();
152
153                if (!"classname".equalsIgnoreCase(type))
154                {
155                    registerFactories(type);
156                }
157            }
158        }
159        catch (TurbineException e)
160        {
161            throw new InitializationException(
162                    "AssemblerBrokerService failed to initialize", e);
163        }
164
165        isCaching = Turbine.getConfiguration()
166            .getBoolean(TurbineConstants.MODULE_CACHE_KEY,
167                        TurbineConstants.MODULE_CACHE_DEFAULT);
168
169        if (isCaching)
170        {
171            int cacheSize = Turbine.getConfiguration()
172                .getInt(TurbineConstants.MODULE_CACHE_SIZE_KEY,
173                        TurbineConstants.MODULE_CACHE_SIZE_DEFAULT);
174
175            assemblerCache = new LRUMap(cacheSize);
176            loaderCache = new LRUMap(cacheSize);
177        }
178
179        setInit(true);
180    }
181
182    /**
183     * Register a new AssemblerFactory
184     *
185     * @param factory factory to register
186     */
187    public <T extends Assembler> void registerFactory(AssemblerFactory<T> factory)
188    {
189        getFactoryGroup(factory.getManagedClass()).add(factory);
190    }
191
192    /**
193     * Attempt to retrieve an Assembler of a given type with
194     * a name.  Cycle through all the registered AssemblerFactory
195     * classes of type and return the first non-null assembly
196     * found.  If an assembly was not found return null.
197     *
198     * @param type type of Assembler
199     * @param name name of the requested Assembler
200     * @return an Assembler or null
201     * @throws TurbineException
202     */
203    @SuppressWarnings("unchecked")
204    public <T extends Assembler> T getAssembler(Class<T> type, String name)
205        throws TurbineException
206    {
207        String key = type + ":" + name;
208        T assembler = null;
209
210        if (isCaching && assemblerCache.containsKey(key))
211        {
212            assembler = (T) assemblerCache.get(key);
213            log.debug("Found " + key + " in the cache!");
214        }
215        else
216        {
217            log.debug("Loading " + key);
218            List<AssemblerFactory<T>> facs = getFactoryGroup(type);
219
220            for (Iterator<AssemblerFactory<T>> it = facs.iterator(); (assembler == null) && it.hasNext();)
221            {
222                AssemblerFactory<T> fac = it.next();
223
224                try
225                {
226                    assembler = fac.getAssembler(name);
227                }
228                catch (Exception e)
229                {
230                    throw new TurbineException("Failed to load an assembler for "
231                                               + name + " from the "
232                                               + type + " factory "
233                                               + fac.getClass().getName(), e);
234                }
235
236                if (assembler != null)
237                {
238                    AnnotationProcessor.process(assembler);
239
240                    if (isCaching)
241                    {
242                        assemblerCache.put(key, assembler);
243                    }
244                }
245            }
246        }
247
248        return assembler;
249    }
250
251    /**
252     * Get a Loader for the given assembler type
253     *
254     * @param type The Type of the Assembler
255     * @return A Loader instance for the requested type
256     */
257    @SuppressWarnings("unchecked")
258    public <T extends Assembler> Loader<T> getLoader(Class<T> type)
259    {
260        Loader<T> loader = null;
261
262        if (isCaching && loaderCache.containsKey(type))
263        {
264            loader = (Loader<T>) loaderCache.get(type);
265            log.debug("Found " + type + " loader in the cache!");
266        }
267        else
268        {
269            log.debug("Getting Loader for " + type);
270            List<AssemblerFactory<T>> facs = getFactoryGroup(type);
271
272            for (Iterator<AssemblerFactory<T>> it = facs.iterator(); (loader == null) && it.hasNext();)
273            {
274                AssemblerFactory<T> fac = it.next();
275                loader = fac.getLoader();
276            }
277
278            if (isCaching && loader != null)
279            {
280                loaderCache.put(type, loader);
281            }
282        }
283
284        if (loader == null)
285        {
286            log.warn("Loader for " + type + " is null.");
287        }
288
289        return loader;
290    }
291}