1 package org.apache.jetspeed.services.template;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
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
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
69
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
158 String templatePaths = props
159 .getProperty("template.path", "/templates");
160
161
162 templatePaths = ServletUtils.expandRelative(config,
163 templatePaths);
164
165
166
167 props.put("template.path", templatePaths);
168
169
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
180 extension = props.getProperty("default.extension", "html");
181
182
183 defaultNavigation = props
184 .getProperty("default.navigation", "TemplateNavigation");
185 defaultScreen = props.getProperty("default.screen", "TemplateScreen");
186
187
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
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
441
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
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
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