View Javadoc

1   /* Copyright (2005-2007) Schibsted Søk AS
2    * This file is part of SESAT.
3    *
4    *   SESAT is free software: you can redistribute it and/or modify
5    *   it under the terms of the GNU Affero General Public License as published by
6    *   the Free Software Foundation, either version 3 of the License, or
7    *   (at your option) any later version.
8    *
9    *   SESAT is distributed in the hope that it will be useful,
10   *   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   *   GNU Affero General Public License for more details.
13   *
14   *   You should have received a copy of the GNU Affero General Public License
15   *   along with SESAT.  If not, see <http://www.gnu.org/licenses/>.
16   *
17   * VelocityEngineFactory.java
18   *
19   * Created on 3 February 2006, 13:24
20   *
21   */
22  
23  package no.sesat.search.view.velocity;
24  
25  import java.util.HashMap;
26  import java.util.Map;
27  import java.util.Properties;
28  import java.util.concurrent.locks.ReentrantReadWriteLock;
29  
30  import javax.xml.parsers.DocumentBuilder;
31  
32  import no.geodata.maputil.CoordHelper;
33  import no.schibstedsok.commons.ioc.BaseContext;
34  import no.schibstedsok.commons.ioc.ContextWrapper;
35  import no.sesat.search.InfrastructureException;
36  import no.sesat.search.result.Boomerang;
37  import no.sesat.search.result.Decoder;
38  import no.sesat.search.site.Site;
39  import no.sesat.search.site.SiteContext;
40  import no.sesat.search.site.SiteKeyedFactory;
41  import no.sesat.search.site.config.BytecodeLoader;
42  import no.sesat.search.site.config.DocumentLoader;
43  import no.sesat.search.site.config.PropertiesLoader;
44  import no.sesat.search.site.config.ResourceContext;
45  import no.sesat.search.site.config.SiteClassLoaderFactory;
46  import no.sesat.search.site.config.SiteConfiguration;
47  import no.sesat.search.site.config.Spi;
48  import no.sesat.search.site.config.UrlResourceLoader;
49  
50  import no.sesat.search.view.navigation.NavigationHelper;
51  import org.apache.log4j.Level;
52  import org.apache.log4j.Logger;
53  import org.apache.velocity.Template;
54  import org.apache.velocity.VelocityContext;
55  import org.apache.velocity.app.Velocity;
56  import org.apache.velocity.app.VelocityEngine;
57  import org.apache.velocity.exception.ParseErrorException;
58  import org.apache.velocity.exception.ResourceNotFoundException;
59  import org.apache.velocity.runtime.RuntimeConstants;
60  import org.apache.velocity.tools.generic.DateTool;
61  import org.apache.velocity.tools.generic.MathTool;
62  import org.apache.velocity.tools.generic.NumberTool;
63  
64  /** Custom Factory around Velocity Engines and Templates.
65   * Each instance maps to an VelocityEngine instance.
66   * All template operations (getting and merging) are done through this class
67   *   rather than directly against Velocity's API.
68   *
69   * <b>Developer Aid</b><br/>
70   * Ola-marius extended the engine so to run in debug mode that outlines (& titles) each rendered template.
71   * See
72   * <a href="http://sesat.no/debugging-velocity-templates.html">
73   *  Debugging Velocity Templates
74   * </a><br/>
75   *
76   * @version $Id: VelocityEngineFactory.java 6596 2008-05-10 10:05:48Z ssmiweve $
77   *
78   */
79  public final class VelocityEngineFactory implements SiteKeyedFactory{
80  
81      /**
82       * The context the AnalysisRules must work against.
83       */
84      public interface Context extends SiteContext, ResourceContext {
85      }
86  
87      // Constants -----------------------------------------------------
88  
89      private static final Logger LOG = Logger.getLogger(VelocityEngineFactory.class);
90  
91      private static final String INFO_TEMPLATE_NOT_FOUND = "Could not find template ";
92      private static final String ERR_IN_TEMPLATE = "Error parsing template ";
93      private static final String ERR_GETTING_TEMPLATE = "Error getting template ";
94  
95      private static final String VELOCITY_LOGGER = "org.apache.velocity";
96  
97      private static final Map<Site,VelocityEngineFactory> INSTANCES = new HashMap<Site,VelocityEngineFactory>();
98      private static final ReentrantReadWriteLock INSTANCES_LOCK = new ReentrantReadWriteLock();
99  
100     private static final String LOGSYSTEM_CLASS = "org.apache.velocity.runtime.log.Log4JLogChute";
101     private static final String LOG_NAME = "runtime.log.logsystem.log4j.logger";
102 
103     private static final boolean VELOCITY_DEBUG = Boolean.getBoolean("VELOCITY_DEBUG");
104 
105     // Attributes ----------------------------------------------------
106 
107     private final VelocityEngine engine;
108 
109     // Static --------------------------------------------------------
110 
111 
112     /** Find the appropriate velocity Template by its name against a given engine and site.
113      * Will throw a ResourceNotFoundException if not found.
114      *
115      * @param engine the VelocityEngine appropriate for the current site.
116      * @param site the current site.
117      * @param templateName the name of the template. must not contain ".vm" suffix.
118      * @return returns the template.
119      * @throws org.apache.velocity.exception.ResourceNotFoundException if the template was not found.
120      */
121     public static Template getTemplate(
122             final VelocityEngine engine,
123             final Site site,
124             final String templateName) throws ResourceNotFoundException{
125 
126         final String templateUrl = site.getTemplateDir() + '/' + templateName + ".vm";
127 
128         try {
129             return engine.getTemplate(templateUrl);
130 
131 
132         } catch (ParseErrorException ex) {
133             LOG.error(ERR_IN_TEMPLATE + templateUrl, ex);
134             throw new InfrastructureException(ex);
135 
136         } catch (ResourceNotFoundException ex) {
137             // throw this so callers know we did not find the resource.
138             throw ex;
139 
140         } catch (Exception ex) {
141             LOG.error(ERR_GETTING_TEMPLATE + templateUrl, ex);
142             throw new InfrastructureException(ex);
143         }
144     }
145 
146     public static VelocityContext newContextInstance(final VelocityEngine engine){
147 
148         final VelocityContext context = new VelocityContext();
149 
150         // coord helper
151         context.put("coordHelper", new CoordHelper());
152         // decoder
153         context.put("decoder", new Decoder());
154         // math tool
155         context.put("math", new MathTool());
156         // number tool
157         context.put("number", new NumberTool());
158         // date tool
159         context.put("date", new DateTool());
160         // navigation helper
161         context.put("navigationHelper", new NavigationHelper());
162         // boomerang
163         context.put("boomerang", new Boomerang());
164 
165         return context;
166     }
167 
168 
169     /** Main method to retrieve the correct VelocityEngine to further obtain
170      * AnalysisRule.
171      * @param cxt the contextual needs the VelocityEngine must use to operate.
172      * @return VelocityEngine for this site.
173      */
174     public static VelocityEngineFactory instanceOf(final Context cxt) {
175 
176         final Site site = cxt.getSite();
177 
178         VelocityEngineFactory instance = null;
179 
180         try {
181             INSTANCES_LOCK.readLock().lock();
182             instance = INSTANCES.get(site);
183         } finally {
184             INSTANCES_LOCK.readLock().unlock();
185         }
186 
187         if(!VELOCITY_DEBUG) {
188             if (instance == null) {
189                 instance = new VelocityEngineFactory(cxt);
190             }
191         } else {
192             instance = new VelocityEngineFactory(cxt);
193         }
194 
195         return instance;
196     }
197 
198     /**
199      * Utility wrapper to the instanceOf(Context).
200      * <b>Makes the presumption we will be using the UrlResourceLoader to load all resources.</b>
201      * @param site the site the VelocityEngine will work for.
202      * @return VelocityEngine for this site.
203      */
204     public static VelocityEngineFactory valueOf(final Site site) {
205 
206         // RegExpEvaluatorFactory.Context for this site & UrlResourceLoader.
207         final VelocityEngineFactory instance = VelocityEngineFactory.instanceOf(new VelocityEngineFactory.Context() {
208             public Site getSite() {
209                 return site;
210             }
211             public PropertiesLoader newPropertiesLoader(
212                     final SiteContext siteCxt,
213                     final String resource,
214                     final Properties properties) {
215 
216                 return UrlResourceLoader.newPropertiesLoader(siteCxt, resource, properties);
217             }
218             public DocumentLoader newDocumentLoader(
219                     final SiteContext siteCxt,
220                     final String resource,
221                     final DocumentBuilder builder) {
222 
223                 return UrlResourceLoader.newDocumentLoader(siteCxt, resource, builder);
224             }
225             public BytecodeLoader newBytecodeLoader(final SiteContext site, final String name, final String jar) {
226                 return UrlResourceLoader.newBytecodeLoader(site, name, jar);
227             }
228 
229         });
230         return instance;
231     }
232 
233 
234     // Constructors --------------------------------------------------
235 
236     /** Creates a new instance of VelocityEngineFactory */
237     private VelocityEngineFactory(final Context cxt) {
238 
239 
240         try{
241             INSTANCES_LOCK.writeLock().lock();
242 
243             final Site site = cxt.getSite();
244 
245             final SiteConfiguration siteConf = SiteConfiguration.instanceOf(ContextWrapper.wrap(
246                     SiteConfiguration.Context.class,
247                     cxt));
248 
249             final StringBuilder directives = new StringBuilder();
250 
251             for(int i=0; i < 10; ++i){
252                 final String d = siteConf.getProperty("velocity.directives." + i);
253 
254                 if(null != d && d.length() > 0){
255 
256                     directives.append(d + ',');
257                 }
258             }
259 
260             // truncate last ','
261             directives.setLength(directives.length()-1);
262 
263             final Logger logger = Logger.getLogger(VELOCITY_LOGGER);
264 
265             engine = new VelocityEngine(){
266                 /** We override this method to dampen the
267                  * <ERROR velocity: ResourceManager : unable to find resource ...>
268                  * error messages in sesam.error
269                  **/
270                 @Override
271                 public Template getTemplate(final String name)
272                         throws ResourceNotFoundException, ParseErrorException, Exception {
273 
274                     final Level level = logger.getLevel();
275                     logger.setLevel(Level.FATAL);
276 
277                     final Template retValue = super.getTemplate(name);
278 
279                     logger.setLevel(level);
280                     return retValue;
281                 }
282 
283             };
284 
285 
286             final ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
287             try{
288 
289                 engine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, LOGSYSTEM_CLASS);
290                 engine.setProperty(LOG_NAME, logger.getName());
291                 engine.setProperty(Velocity.RESOURCE_LOADER, "url");
292                 engine.setProperty("url.resource.loader.class", URLVelocityTemplateLoader.class.getName());
293 
294                 if(VELOCITY_DEBUG) {
295                     engine.setProperty("url.resource.loader.cache", "false");
296                     engine.setProperty("velocimacro.library.autoreload", "true");
297                 } else {
298                 	engine.setProperty("url.resource.loader.cache", "true");
299                 	engine.setProperty("url.resource.loader.modificationCheckInterval", "60");
300                     engine.setProperty(Velocity.RESOURCE_MANAGER_CLASS, QuickResourceManagerImpl.class.getName());
301                     engine.setProperty(Velocity.RESOURCE_MANAGER_CACHE_CLASS, QuickResourceCacheImpl.class.getName());
302                     engine.setProperty(Velocity.RESOURCE_MANAGER_DEFAULTCACHE_SIZE, "0");
303 
304                     // Use custom unbound quicker resource cache.
305                     engine.setProperty(QuickResourceCacheImpl.INITIAL_SIZE_PROPERTY, 1000);
306                 }
307 
308                 engine.setProperty(Site.NAME_KEY, site);
309 
310                 engine.setProperty("input.encoding", "UTF-8");
311                 engine.setProperty("output.encoding", "UTF-8");
312                 engine.setProperty("userdirective", directives.toString());
313                 engine.setProperty(
314                         "velocimacro.library",
315                         site.getTemplateDir() + "/VM_sesat_library.vm,"
316                         + site.getTemplateDir() + "/VM_global_library.vm,"
317                         + site.getTemplateDir() + "/VM_site_library.vm,"
318                         + site.getTemplateDir() + "/VM_map_library.vm"); //XXX not happy with this. it isn't SESAT.
319 
320                 final SiteClassLoaderFactory.Context classContext = ContextWrapper.wrap(
321                         SiteClassLoaderFactory.Context.class,
322                         new BaseContext() {
323                             public Site getSite(){
324                                 return site;
325                             }
326                             public Spi getSpi() {
327                                 return Spi.VELOCITY_DIRECTIVES;
328                             }
329                         },
330                         cxt);
331 
332                 final ClassLoader ctrlClassLoader = SiteClassLoaderFactory.instanceOf(classContext).getClassLoader();
333                 Thread.currentThread().setContextClassLoader(ctrlClassLoader);
334                 engine.init();
335 
336             }catch (Exception e) {
337                 throw new InfrastructureException(e);
338             }finally{
339                 Thread.currentThread().setContextClassLoader(origLoader);
340             }
341 
342             INSTANCES.put(site, this);
343         }finally{
344             INSTANCES_LOCK.writeLock().unlock();
345         }
346     }
347 
348     // Public --------------------------------------------------------
349 
350     public VelocityEngine getEngine() {
351         return engine;
352     }
353 
354     public boolean remove(final Site site) {
355 
356         try{
357             INSTANCES_LOCK.writeLock().lock();
358             return null != INSTANCES.remove(site);
359         }finally{
360             INSTANCES_LOCK.writeLock().unlock();
361         }
362     }
363 
364         // Package protected ---------------------------------------------
365 
366         // Protected -----------------------------------------------------
367 
368         // Private -------------------------------------------------------
369 
370 
371     // Inner classes -------------------------------------------------
372 }