View Javadoc

1   package org.apache.jetspeed.services.template;
2   
3   /*
4    * Copyright 2000-2004 The Apache Software Foundation.
5    * 
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    * 
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  
20  
21  // Java Core Classes
22  import java.util.ArrayList;
23  import java.util.Properties;
24  import java.util.Hashtable;
25  import java.util.List;
26  import java.util.StringTokenizer;
27  import java.io.File;
28  import javax.servlet.ServletConfig;
29  
30  // Turbine Utility Classes
31  import org.apache.turbine.util.ServletUtils;
32  import org.apache.turbine.services.TurbineBaseService;
33  import org.apache.turbine.services.InitializationException;
34  import org.apache.turbine.services.resources.TurbineResources;
35  import org.apache.turbine.modules.ScreenLoader;
36  import org.apache.turbine.modules.NavigationLoader;
37  
38  // Jetspeed classes
39  import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
40  import org.apache.jetspeed.services.logging.JetspeedLogger;
41  
42  /***
43   * <p>This service extends the TurbineTemplateService to modify its behaviour:
44   * Not only layout and screen packages, but also the screen templates
45   * are searched in the template neames filepath, so that a fallback
46   * strategy is provided, that can be used for multi-language, multi-device 
47   * and browser-specific support support.</p>
48   * <p>E.g: a template name "/html/en/US/IE/mytemplate" would search for 
49   * following files (in the given order):
50   * <ol>
51   * <li>. /html/en/US/IE/mytemplate</li>
52   * <li>. /html/en/US/mytemplate</li>
53   * <li>. /html/en/mytemplate</li>
54   * <li>. /html/mytemplate</li>
55   * <li>. /mytemplate</li>
56   * </ol>
57   * </p>
58   * <p>
59   * TurbineTemplateService part:
60   * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a>
61   * @author <a href="mailto:mbryson@mont.mindspring.com">Dave Bryson</a>
62   * JetspeedTemplateService part:
63   * @author <a href="mailto:ingo@apache.org">Ingo Schuster</a>
64   * @version $Id: JetspeedTemplateService.java,v 1.11 2004/02/23 03:38:54 jford Exp $
65   */
66  public class JetspeedTemplateService
67      extends TurbineBaseService
68    //   implements TemplateService
69    // removed dst: 2001/06/03, TDK 2.2 integration
70  {
71      /***
72       * Static initialization of the logger for this class
73       */    
74      private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(JetspeedTemplateService.class.getName());
75      
76      /*** The hashtable used to cache Screen names. */
77      private Hashtable screenCache = null;
78  
79      /*** The hashtable used to cache screen template names. */
80      private Hashtable templateCache = null;
81  
82      /*** The hashtable used to cache Navigation names. */
83      private Hashtable navCache = null;
84  
85      /*** The hashtable used to cache layout template names. */
86      private Hashtable layoutCache = null;
87  
88      /*** Flag set if cache is to be used. */
89      private boolean useCache = false;
90  
91      /*** Default extension. */
92      private String extension;
93  
94      /*** Default layout template. */
95      private String defaultLayoutTemplate;
96  
97      /*** Default Navigation module. */
98      private String defaultNavigation;
99  
100     /*** Default Screen module. */
101     private String defaultScreen;
102 
103     /***
104      * The absolute paths where the appropriate template engine will
105      * be searching for templates.
106      */
107     private String[] templateRoot = null;
108 
109     /***
110      * Called the first time the Service is used.
111      *
112      * @param config A ServletConfig.
113      */
114     public void init(ServletConfig config)
115         throws InitializationException
116     {
117         try
118         {
119             initTemplate(config);
120             setInit(true);
121             logger.info ("TemplateService init()....finished!");
122         }
123         catch (Exception e)
124         {
125             logger.error( "TurbineTemplateService failed to initialize", e );
126             throw new InitializationException("TurbineTemplateService failed to initialize", e);
127         }
128     }
129 
130     /***
131      * TODO: Document this class.
132      *
133      * @param config A ServletConfig.
134      * @exception Exception, a generic exception.
135      */
136     private void initTemplate(ServletConfig config)
137         throws Exception
138     {
139         useCache = TurbineResources.getBoolean("modules.cache", true);
140         Properties props = getProperties();
141 
142         if (useCache)
143         {
144             int layoutSize = Integer
145                 .parseInt(props.getProperty("layout.cache.size", "5"));
146             int navigationSize = Integer
147                 .parseInt(props.getProperty("navigation.cache.size", "10"));
148             int screenSize = Integer
149                 .parseInt(props.getProperty("screen.cache.size", "5"));
150             int templateSize = Integer
151                 .parseInt(props.getProperty("screen.cache.size", "50"));
152             layoutCache = new Hashtable( (int)(1.25*layoutSize) + 1);
153             navCache = new Hashtable( (int)(1.25*navigationSize) + 1);
154             screenCache = new Hashtable( (int)(1.25*screenSize) + 1);
155             templateCache = new Hashtable( (int)(1.25*templateSize) + 1);
156         }
157         // relative to the webapp root directory
158         String templatePaths = props
159             .getProperty("template.path", "/templates");
160 
161         // If possible, transform paths to be webapp root relative.
162         templatePaths = ServletUtils.expandRelative(config,
163                                                      templatePaths);
164 
165         // store the converted paths in service properties for 
166         // Turbine based providers 
167         props.put("template.path", templatePaths);
168 
169         // tokenize the template.path property and assign to an array
170         String pathSep = System.getProperty("path.separator");
171         StringTokenizer st = new StringTokenizer(templatePaths,pathSep);
172         templateRoot = new String[st.countTokens()];
173         int pos = 0;
174         while(st.hasMoreTokens())
175         {
176             templateRoot[pos++] = st.nextToken();
177         }
178 
179         // the extension that is added to layout templates (e.g.)
180         extension = props.getProperty("default.extension", "html");              
181 
182         // the default modules 
183         defaultNavigation = props
184             .getProperty("default.navigation", "TemplateNavigation");
185         defaultScreen = props.getProperty("default.screen", "TemplateScreen");  
186         
187         // the default layout template
188         defaultLayoutTemplate = props
189             .getProperty("default.layout.template", "/default." + extension);
190         
191         if (defaultLayoutTemplate.indexOf('.') == -1)
192         {
193             defaultLayoutTemplate = defaultLayoutTemplate + "." + extension;
194         }
195     }
196 
197     /***
198      *  Adds the object into the hashtable.
199      *
200      * @param key The String key for the object.
201      * @param value The Object.
202      * @param h The Hashtable.
203      */
204     private void addToCache ( String key,
205                               Object value,
206                               Hashtable h )
207     {
208         if (useCache && value != null)
209         {
210             h.put(key, value);
211         }
212     }
213 
214     /***
215      * Get the Screen template given in the properties file.
216      *
217      * @return A String which is the value of the TemplateService 
218      * default.screen property.
219      */
220     public String getDefaultScreen()
221     {
222         return defaultScreen;
223     }
224     
225     /***
226      * Get the default Navigation given in the properties file.
227      *
228      * @return A String which is the value of the TemplateService 
229      * default.navigation property.
230      */
231     public String getDefaultNavigation()
232     {
233         return defaultNavigation;
234     }
235 
236     /***
237      * Get the default layout template given in the properties file.
238      *
239      * @return A String which is the value of the TemplateService 
240      * default.layout.template property.
241      */
242     public String getDefaultLayoutTemplate()
243     {
244         return defaultLayoutTemplate;
245     }
246 
247     
248     /***
249      * Locate and return the name of a screen template.
250      *
251      *
252      * @param name A String which is the key to the template.
253      * @return A String with the screen template path.
254      * @exception Exception, a generic exception.
255      */
256     public String getScreenTemplateName(String key)
257         throws Exception
258     {
259         if (name==null)
260             throw new Exception ("TurbineTemplateService: " + 
261                 "getLayoutTemplateName() was passed in a null value.");
262 
263         String name = null;
264 
265         if (  useCache && templateCache.containsKey(key) )
266         {
267             name = (String)templateCache.get(key);
268         }
269         else
270         {
271             if ( logger.isDebugEnabled() )
272             {
273                 logger.debug("JetspeedTemplatePage.getLayoutTemplateName(" + key + ")");
274             }
275             String[] names = parseScreenTemplate(key);
276             name = names[2];
277             addToCache( key, names[0], screenCache );
278             addToCache( key, names[1], layoutCache );
279             addToCache( key, names[2], templateCache );
280         }
281         return name;
282     }
283     /***
284      * Locate and return the name of a layout template.
285      *
286      *
287      * @param name A String with the name of the template.
288      * @return A String with the layout template path.
289      * @exception Exception, a generic exception.
290      */
291     public String getLayoutTemplateName(String name)
292         throws Exception
293     {
294         if (name==null)
295             throw new Exception ("TurbineTemplateService: " + 
296                 "getLayoutTemplateName() was passed in a null value.");
297 
298         String layoutName = null;
299 
300         if (  useCache && layoutCache.containsKey(name) )
301         {
302             layoutName = (String)layoutCache.get(name);
303         }
304         else
305         {
306             String[] names = parseScreenTemplate(name);
307             layoutName = names[1];
308             addToCache( name, names[0], screenCache );
309             addToCache( name, names[1], layoutCache );
310             addToCache( name, names[2], templateCache );
311         }
312         return layoutName;
313     }
314 
315     /***
316      * Locate and return the name of a Navigation module.
317      *
318      * @param name A String with the name of the template.
319      * @return A String with the name of the navigation.
320      * @exception Exception, a generic exception.
321      */
322     public String getNavigationName(String name)
323         throws Exception
324     {
325         if (name==null)
326             throw new Exception ("TurbineTemplateService: " + 
327                 "getNavigationName() was passed in a null value.");
328 
329         String nav_name = null;
330 
331         if (  useCache && navCache.containsKey(name) )
332         {
333             nav_name = (String)navCache.get(name);
334         }
335         else
336         {
337             nav_name = parseNavigationTemplate(name);
338             addToCache( name, nav_name, navCache );
339         }
340         return nav_name;
341     }
342 
343     /***
344      * Locate and return the name of a Screen module.
345      *
346      * @param name A String with the name of the template.
347      * @return A String with the name of the screen.
348      * @exception Exception, a generic exception.
349      */
350     public String getScreenName(String name)
351         throws Exception
352     {
353 
354         if (name==null)
355             throw new Exception ("TurbineTemplateService: " + 
356                 "getScreenName() was passed in a null value.");
357 
358         String screenName = null;
359 
360         if (  useCache && screenCache.containsKey(name) )
361         {
362             screenName = (String)screenCache.get(name);
363         }
364         else
365         {
366             String[] names = parseScreenTemplate(name);
367             screenName = names[0];
368             addToCache( name, names[0], screenCache );
369             addToCache( name, names[1], layoutCache );
370             addToCache( name, names[2], templateCache );
371         }
372         return screenName;
373     }
374 
375     /***
376      * Get the default extension given in the properties file.
377      *
378      * @return A String with the extension.
379      */
380     public String getDefaultExtension()
381     {
382         return extension;
383     }
384 
385     /***
386      * This method takes the template parameter and parses it, so that
387      * relevant Screen/Layout-template information can be extracted.
388      *
389      * @param template A String with the template name.
390      * @return A String[] where the first element is the Screen name
391      *         and the second element is the layout template.
392      */
393     protected String[] parseScreenTemplate( String template ) throws Exception
394     {
395         // check if an extension was included.  if not, add the default
396         if ( template.indexOf('.') == -1 )
397         {
398             template = template + "." + getDefaultExtension(); 
399         }
400 
401         if ( logger.isDebugEnabled() )
402         {
403             logger.debug("JetspeedTemplateService.parseScreen: template = " + template);
404         }
405         
406         StringTokenizer st = new StringTokenizer(template, "/");
407         List tokens = new ArrayList(st.countTokens());
408         while(st.hasMoreTokens())
409         {
410             String token = st.nextToken();
411             if (!token.equals(""))
412             {
413                 tokens.add(token);
414             }
415         }
416         if ( logger.isDebugEnabled() )
417         {
418             logger.debug("JetspeedTemplateService.parseScreen: tokens1: " + tokens);
419         }
420         String fileName = (String)tokens.get(tokens.size() - 1);
421         tokens.remove(tokens.size()-1);
422         int dot = fileName.lastIndexOf('.');
423         String className = null;
424         if (dot>0)
425         {
426             className = fileName.substring(0, dot);
427         }
428         else
429         {
430             className = fileName;
431         }
432         String firstChar = String.valueOf(className.charAt(0));
433         firstChar = firstChar.toUpperCase();
434         className = firstChar + className.substring(1);
435         if ( logger.isDebugEnabled() )
436         {
437             logger.debug("JetspeedTemplateService.parseScreen: tokens2: " + tokens);
438         }
439 
440         // make sure the template exists and determine the correct
441         // templateRoot path
442         String pathRoot = null;
443         String allPaths = "";
444         String pathSep = System.getProperty("path.separator");
445         for (int i=0; i<templateRoot.length; i++)
446         {
447             if ( logger.isDebugEnabled() )
448             {
449                 logger.debug("JetspeedTemplateService.parseScreen: templateRoot " + i + " " + templateRoot[i]);
450             }
451 
452             String templatePath = null;
453         
454             for (int k=tokens.size(); k>=0;  k--)
455             {
456                 StringBuffer path = new StringBuffer();
457                 for (int j=0; j<k; j++)
458                 {
459                     path.append("/").append((String)tokens.get(j));
460                 }
461                 StringBuffer distinctPath = new StringBuffer(path.toString()).append("/").append(fileName);
462                 templatePath = distinctPath.toString();
463                 if ( logger.isDebugEnabled() )
464                 {
465                     logger.debug("JetspeedTemplateService.parseScreen: Path: " + templatePath);
466                 }
467          
468                 if (new File(templateRoot[i] + "/screens" + templatePath).exists())
469                 {
470                     template = templatePath;
471                     if ( logger.isDebugEnabled() )
472                     {
473                         logger.debug("JetspeedTemplateService.parseScreen: template found: " + template);
474                     }
475                     break;
476                 }
477                 templatePath = null;
478             }
479             if (templatePath != null) {
480                 pathRoot = templateRoot[i];
481                 if ( logger.isDebugEnabled() )
482                 {
483                     logger.debug("JetspeedTemplateService.parseScreen: pathRoot: " + pathRoot);
484                 }
485                 break;
486             }
487             allPaths += pathSep + templateRoot[i];
488         }
489         if (pathRoot == null)
490         {
491             throw new Exception("The screen template: " +
492                                 template +
493                                 " does not exist in " +
494                                 allPaths.substring(pathSep.length()) +
495                                 ", so the TemplateService could not " +
496                                 "determine associated templates.");
497         }
498 
499         /*
500         String[] paths = new String[tokens.size() + 2];
501         String[] pkgs = new String[tokens.size() + 2];
502         int arrayIndex = 0;
503         for (int i=tokens.size(); i>=0;  i--)
504         {
505             StringBuffer path = new StringBuffer();
506             StringBuffer pkg = new StringBuffer();
507             for (int j=0; j<i; j++)
508             {
509                 path.append("/").append((String)tokens.get(j));
510                 pkg.append((String)tokens.get(j)).append('.');
511             }
512             if ( i == tokens.size() )
513             {
514                 StringBuffer distinctPath = new StringBuffer(path.toString());
515                 StringBuffer distinctPkg = new StringBuffer(pkg.toString());
516                 paths[arrayIndex] = distinctPath.append('/').append(fileName).toString();
517                 pkgs[arrayIndex] = distinctPkg.append(className).toString();
518                 arrayIndex++;
519             }
520             paths[arrayIndex] = path.append(defaultLayoutTemplate).toString();
521             pkgs[arrayIndex] = pkg.append("Default").toString();
522             arrayIndex++;
523         }
524         */        
525 
526         String[] paths = new String[2 * tokens.size() +2];
527         String[] pkgs  = new String[2 * tokens.size() +2];
528         int arrayIndex = 0;
529         for (int i=tokens.size(); i>=0;  i--)
530         {
531             StringBuffer path = new StringBuffer();
532             StringBuffer pkg = new StringBuffer();
533             for (int j=0; j<i; j++)
534             {
535                 path.append("/").append((String)tokens.get(j));
536                 pkg.append((String)tokens.get(j)).append('.');
537             }
538             paths[arrayIndex] = path.append("/").append(fileName).toString();
539             pkgs[arrayIndex]  = pkg.append("/").append(className).toString();
540             arrayIndex++;
541         }
542         
543         for (int i=tokens.size(); i>=0;  i--)
544         {
545             StringBuffer path = new StringBuffer();
546             StringBuffer pkg = new StringBuffer();
547             for (int j=0; j<i; j++)
548             {
549                 path.append("/").append((String)tokens.get(j));
550                 pkg.append((String)tokens.get(j)).append('.');
551             }
552             paths[arrayIndex] = path.append(defaultLayoutTemplate).toString();
553             pkgs[arrayIndex]  = pkg.append("Default").toString();
554             arrayIndex++;
555         }
556 
557         if ( logger.isDebugEnabled() )
558         {
559             for (int i=0; i<paths.length; i++)
560             {
561                 logger.debug("JetspeedTemplateService.parseScreen: paths[" + i + "] = " + paths[i]);
562             }
563         }
564 
565         String[] holder = new String[3];
566         holder[0] = getScreenName(pkgs);
567         holder[1] = getLayoutTemplateName(pathRoot, paths);
568         holder[2] = template;
569         return holder;
570     }
571 
572     /***
573      * Parse the template name out to a package path to locate the
574      * Navigation module.  This is different than the Screen/Layout
575      * parser in that it only looks for packages.  Note: If caching is
576      * enabled, this is only performed once for each unique template.
577      *
578      * @param String The template name (i.e folder/headernav.wm).
579      * @return A String with the name of the Navigation module to use
580      * for the template.
581      */
582     protected String parseNavigationTemplate( String template )
583     {
584         StringTokenizer st = new StringTokenizer(template, "/");
585         List tokens = new ArrayList(st.countTokens());
586         while(st.hasMoreTokens())
587         {
588             String token = st.nextToken();
589             if (!token.equals(""))
590             {
591                 tokens.add(token);
592             }
593         }
594         String fileName = (String)tokens.get(tokens.size() - 1);
595         tokens.remove(tokens.size() - 1);
596         int dot = fileName.lastIndexOf('.');
597         String className = null;
598         if (dot>0)
599         {
600             className = fileName.substring(0, dot);
601         }
602         else
603         {
604             className = fileName;
605         }
606         String firstChar = String.valueOf(className.charAt(0));
607         firstChar = firstChar.toUpperCase();
608         className = firstChar + className.substring(1);
609 
610         String[] pkgs = new String[tokens.size() + 2];
611         int arrayIndex = 0;
612         for (int i=tokens.size(); i>=0;  i--)
613         {
614             StringBuffer pkg = new StringBuffer();
615             for (int j=0; j<i; j++)
616             {
617                 pkg.append((String)tokens.get(j)).append('.');
618             }
619             if ( i == tokens.size() )
620             {
621                 StringBuffer distinctPkg = new StringBuffer(pkg.toString());
622                 pkgs[arrayIndex] = distinctPkg.append(className).toString();
623                 arrayIndex++;
624             }
625             pkgs[arrayIndex] = pkg.append("Default").toString();
626             arrayIndex++;
627         }
628         return getNavigationName( pkgs);
629     }
630 
631     /***
632      * Extract possible layouts paths.
633      *
634      * @param possiblePaths A String[] with possible paths to search.
635      * @return A String with the name of the layout template.
636      */
637     private String getLayoutTemplateName(String pathRoot, String[] possiblePaths)
638     {
639         if ( logger.isDebugEnabled() )
640         {
641             logger.debug("JetspeedTemplatePage.getLayoutTemplateName: pathRoot " + pathRoot);
642         
643             for (int i=0; i<possiblePaths.length; i++)
644             {
645                 logger.debug("JetspeedTemplatePage.getLayoutTemplateName: possiblePaths[" + i + "]=" + possiblePaths[i]);
646             }
647         }
648         for (int i=0; i<possiblePaths.length; i++)
649         {
650             if (new File(pathRoot, "layouts" + possiblePaths[i]).exists())
651             {
652                 if ( logger.isDebugEnabled() )
653                 {
654                     logger.debug("JetspeedTemplatePage.getLayoutTemplateName: " + pathRoot + "/layouts" + possiblePaths[i] + " found.");
655                 }
656                 return possiblePaths[i];
657             } 
658             else 
659             {
660                 if ( logger.isDebugEnabled() )
661                 {
662                     logger.debug("JetspeedTemplatePage.getLayoutTemplateName: " + pathRoot + "/layouts" + possiblePaths[i] + " NOT found.");
663                 }
664             }
665         }
666         return defaultLayoutTemplate;
667     }
668 
669     /***
670      * Extract a possible Screen from the packages.
671      *
672      * @param possibleScreens A String[] with possible paths to
673      * search.
674      * @return A String with the name of the Screen class to use.
675      */
676     private String getScreenName( String[] possibleScreens)
677     {
678         for (int i=0; i<possibleScreens.length; i++)
679         {
680             try
681             {
682                 ScreenLoader.getInstance().getInstance(possibleScreens[i]);
683                 return possibleScreens[i];
684             }
685             catch (Exception e) 
686             {
687                 logger.error( "Exception in getScreenName", e );
688             }
689         }
690         return defaultScreen;
691     }
692 
693 
694     /***
695      * Seaches for the Navigation class that may match the
696      * name of the Navigation template.
697      *
698      * @param possibleNavigations A String[] with possible navigation
699      * packages.
700      * @return A String with the name of the Navigation class to use.
701      */
702     private String getNavigationName( String[] possibleNavigations)
703     {
704         for (int i=0; i<possibleNavigations.length; i++)
705         {
706             try
707             {
708                 NavigationLoader.getInstance().getInstance(possibleNavigations[i]);
709                 return possibleNavigations[i];
710             }
711             catch (Exception e) 
712             {
713                 logger.error( "Exception in getNavigationName", e );
714             }
715         }
716         return defaultNavigation;
717     }
718 }
719 
720 
721