package org.apache.jetspeed.services.template; /* * Copyright 2000-2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Java Core Classes import java.util.ArrayList; import java.util.Properties; import java.util.Hashtable; import java.util.List; import java.util.StringTokenizer; import java.io.File; import javax.servlet.ServletConfig; // Turbine Utility Classes import org.apache.turbine.util.ServletUtils; import org.apache.turbine.services.TurbineBaseService; import org.apache.turbine.services.InitializationException; import org.apache.turbine.services.resources.TurbineResources; import org.apache.turbine.modules.ScreenLoader; import org.apache.turbine.modules.NavigationLoader; // Jetspeed classes import org.apache.jetspeed.services.logging.JetspeedLogFactoryService; import org.apache.jetspeed.services.logging.JetspeedLogger; /** * <p>This service extends the TurbineTemplateService to modify its behaviour: * Not only layout and screen packages, but also the screen templates * are searched in the template neames filepath, so that a fallback * strategy is provided, that can be used for multi-language, multi-device * and browser-specific support support.</p> * <p>E.g: a template name "/html/en/US/IE/mytemplate" would search for * following files (in the given order): * <ol> * <li>. /html/en/US/IE/mytemplate</li> * <li>. /html/en/US/mytemplate</li> * <li>. /html/en/mytemplate</li> * <li>. /html/mytemplate</li> * <li>. /mytemplate</li> * </ol> * </p> * <p> * TurbineTemplateService part: * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a> * @author <a href="mailto:mbryson@mont.mindspring.com">Dave Bryson</a> * JetspeedTemplateService part: * @author <a href="mailto:ingo@apache.org">Ingo Schuster</a> * @version $Id: JetspeedTemplateService.java,v 1.11 2004/02/23 03:38:54 jford Exp $ */ public class JetspeedTemplateService extends TurbineBaseService // implements TemplateService // removed dst: 2001/06/03, TDK 2.2 integration { /** * Static initialization of the logger for this class */ private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(JetspeedTemplateService.class.getName()); /** The hashtable used to cache Screen names. */ private Hashtable screenCache = null; /** The hashtable used to cache screen template names. */ private Hashtable templateCache = null; /** The hashtable used to cache Navigation names. */ private Hashtable navCache = null; /** The hashtable used to cache layout template names. */ private Hashtable layoutCache = null; /** Flag set if cache is to be used. */ private boolean useCache = false; /** Default extension. */ private String extension; /** Default layout template. */ private String defaultLayoutTemplate; /** Default Navigation module. */ private String defaultNavigation; /** Default Screen module. */ private String defaultScreen; /** * The absolute paths where the appropriate template engine will * be searching for templates. */ private String[] templateRoot = null; /** * Called the first time the Service is used. * * @param config A ServletConfig. */ public void init(ServletConfig config) throws InitializationException { try { initTemplate(config); setInit(true); logger.info ("TemplateService init()....finished!"); } catch (Exception e) { logger.error( "TurbineTemplateService failed to initialize", e ); throw new InitializationException("TurbineTemplateService failed to initialize", e); } } /** * TODO: Document this class. * * @param config A ServletConfig. * @exception Exception, a generic exception. */ private void initTemplate(ServletConfig config) throws Exception { useCache = TurbineResources.getBoolean("modules.cache", true); Properties props = getProperties(); if (useCache) { int layoutSize = Integer .parseInt(props.getProperty("layout.cache.size", "5")); int navigationSize = Integer .parseInt(props.getProperty("navigation.cache.size", "10")); int screenSize = Integer .parseInt(props.getProperty("screen.cache.size", "5")); int templateSize = Integer .parseInt(props.getProperty("screen.cache.size", "50")); layoutCache = new Hashtable( (int)(1.25*layoutSize) + 1); navCache = new Hashtable( (int)(1.25*navigationSize) + 1); screenCache = new Hashtable( (int)(1.25*screenSize) + 1); templateCache = new Hashtable( (int)(1.25*templateSize) + 1); } // relative to the webapp root directory String templatePaths = props .getProperty("template.path", "/templates"); // If possible, transform paths to be webapp root relative. templatePaths = ServletUtils.expandRelative(config, templatePaths); // store the converted paths in service properties for // Turbine based providers props.put("template.path", templatePaths); // tokenize the template.path property and assign to an array String pathSep = System.getProperty("path.separator"); StringTokenizer st = new StringTokenizer(templatePaths,pathSep); templateRoot = new String[st.countTokens()]; int pos = 0; while(st.hasMoreTokens()) { templateRoot[pos++] = st.nextToken(); } // the extension that is added to layout templates (e.g.) extension = props.getProperty("default.extension", "html"); // the default modules defaultNavigation = props .getProperty("default.navigation", "TemplateNavigation"); defaultScreen = props.getProperty("default.screen", "TemplateScreen"); // the default layout template defaultLayoutTemplate = props .getProperty("default.layout.template", "/default." + extension); if (defaultLayoutTemplate.indexOf('.') == -1) { defaultLayoutTemplate = defaultLayoutTemplate + "." + extension; } } /** * Adds the object into the hashtable. * * @param key The String key for the object. * @param value The Object. * @param h The Hashtable. */ private void addToCache ( String key, Object value, Hashtable h ) { if (useCache && value != null) { h.put(key, value); } } /** * Get the Screen template given in the properties file. * * @return A String which is the value of the TemplateService * default.screen property. */ public String getDefaultScreen() { return defaultScreen; } /** * Get the default Navigation given in the properties file. * * @return A String which is the value of the TemplateService * default.navigation property. */ public String getDefaultNavigation() { return defaultNavigation; } /** * Get the default layout template given in the properties file. * * @return A String which is the value of the TemplateService * default.layout.template property. */ public String getDefaultLayoutTemplate() { return defaultLayoutTemplate; } /** * Locate and return the name of a screen template. * * * @param name A String which is the key to the template. * @return A String with the screen template path. * @exception Exception, a generic exception. */ public String getScreenTemplateName(String key) throws Exception { if (name==null) throw new Exception ("TurbineTemplateService: " + "getLayoutTemplateName() was passed in a null value."); String name = null; if ( useCache && templateCache.containsKey(key) ) { name = (String)templateCache.get(key); } else { if ( logger.isDebugEnabled() ) { logger.debug("JetspeedTemplatePage.getLayoutTemplateName(" + key + ")"); } String[] names = parseScreenTemplate(key); name = names[2]; addToCache( key, names[0], screenCache ); addToCache( key, names[1], layoutCache ); addToCache( key, names[2], templateCache ); } return name; } /** * Locate and return the name of a layout template. * * * @param name A String with the name of the template. * @return A String with the layout template path. * @exception Exception, a generic exception. */ public String getLayoutTemplateName(String name) throws Exception { if (name==null) throw new Exception ("TurbineTemplateService: " + "getLayoutTemplateName() was passed in a null value."); String layoutName = null; if ( useCache && layoutCache.containsKey(name) ) { layoutName = (String)layoutCache.get(name); } else { String[] names = parseScreenTemplate(name); layoutName = names[1]; addToCache( name, names[0], screenCache ); addToCache( name, names[1], layoutCache ); addToCache( name, names[2], templateCache ); } return layoutName; } /** * Locate and return the name of a Navigation module. * * @param name A String with the name of the template. * @return A String with the name of the navigation. * @exception Exception, a generic exception. */ public String getNavigationName(String name) throws Exception { if (name==null) throw new Exception ("TurbineTemplateService: " + "getNavigationName() was passed in a null value."); String nav_name = null; if ( useCache && navCache.containsKey(name) ) { nav_name = (String)navCache.get(name); } else { nav_name = parseNavigationTemplate(name); addToCache( name, nav_name, navCache ); } return nav_name; } /** * Locate and return the name of a Screen module. * * @param name A String with the name of the template. * @return A String with the name of the screen. * @exception Exception, a generic exception. */ public String getScreenName(String name) throws Exception { if (name==null) throw new Exception ("TurbineTemplateService: " + "getScreenName() was passed in a null value."); String screenName = null; if ( useCache && screenCache.containsKey(name) ) { screenName = (String)screenCache.get(name); } else { String[] names = parseScreenTemplate(name); screenName = names[0]; addToCache( name, names[0], screenCache ); addToCache( name, names[1], layoutCache ); addToCache( name, names[2], templateCache ); } return screenName; } /** * Get the default extension given in the properties file. * * @return A String with the extension. */ public String getDefaultExtension() { return extension; } /** * This method takes the template parameter and parses it, so that * relevant Screen/Layout-template information can be extracted. * * @param template A String with the template name. * @return A String[] where the first element is the Screen name * and the second element is the layout template. */ protected String[] parseScreenTemplate( String template ) throws Exception { // check if an extension was included. if not, add the default if ( template.indexOf('.') == -1 ) { template = template + "." + getDefaultExtension(); } if ( logger.isDebugEnabled() ) { logger.debug("JetspeedTemplateService.parseScreen: template = " + template); } StringTokenizer st = new StringTokenizer(template, "/"); List tokens = new ArrayList(st.countTokens()); while(st.hasMoreTokens()) { String token = st.nextToken(); if (!token.equals("")) { tokens.add(token); } } if ( logger.isDebugEnabled() ) { logger.debug("JetspeedTemplateService.parseScreen: tokens1: " + tokens); } String fileName = (String)tokens.get(tokens.size() - 1); tokens.remove(tokens.size()-1); int dot = fileName.lastIndexOf('.'); String className = null; if (dot>0) { className = fileName.substring(0, dot); } else { className = fileName; } String firstChar = String.valueOf(className.charAt(0)); firstChar = firstChar.toUpperCase(); className = firstChar + className.substring(1); if ( logger.isDebugEnabled() ) { logger.debug("JetspeedTemplateService.parseScreen: tokens2: " + tokens); } // make sure the template exists and determine the correct // templateRoot path String pathRoot = null; String allPaths = ""; String pathSep = System.getProperty("path.separator"); for (int i=0; i<templateRoot.length; i++) { if ( logger.isDebugEnabled() ) { logger.debug("JetspeedTemplateService.parseScreen: templateRoot " + i + " " + templateRoot[i]); } String templatePath = null; for (int k=tokens.size(); k>=0; k--) { StringBuffer path = new StringBuffer(); for (int j=0; j<k; j++) { path.append("/").append((String)tokens.get(j)); } StringBuffer distinctPath = new StringBuffer(path.toString()).append("/").append(fileName); templatePath = distinctPath.toString(); if ( logger.isDebugEnabled() ) { logger.debug("JetspeedTemplateService.parseScreen: Path: " + templatePath); } if (new File(templateRoot[i] + "/screens" + templatePath).exists()) { template = templatePath; if ( logger.isDebugEnabled() ) { logger.debug("JetspeedTemplateService.parseScreen: template found: " + template); } break; } templatePath = null; } if (templatePath != null) { pathRoot = templateRoot[i]; if ( logger.isDebugEnabled() ) { logger.debug("JetspeedTemplateService.parseScreen: pathRoot: " + pathRoot); } break; } allPaths += pathSep + templateRoot[i]; } if (pathRoot == null) { throw new Exception("The screen template: " + template + " does not exist in " + allPaths.substring(pathSep.length()) + ", so the TemplateService could not " + "determine associated templates."); } /* String[] paths = new String[tokens.size() + 2]; String[] pkgs = new String[tokens.size() + 2]; int arrayIndex = 0; for (int i=tokens.size(); i>=0; i--) { StringBuffer path = new StringBuffer(); StringBuffer pkg = new StringBuffer(); for (int j=0; j<i; j++) { path.append("/").append((String)tokens.get(j)); pkg.append((String)tokens.get(j)).append('.'); } if ( i == tokens.size() ) { StringBuffer distinctPath = new StringBuffer(path.toString()); StringBuffer distinctPkg = new StringBuffer(pkg.toString()); paths[arrayIndex] = distinctPath.append('/').append(fileName).toString(); pkgs[arrayIndex] = distinctPkg.append(className).toString(); arrayIndex++; } paths[arrayIndex] = path.append(defaultLayoutTemplate).toString(); pkgs[arrayIndex] = pkg.append("Default").toString(); arrayIndex++; } */ String[] paths = new String[2 * tokens.size() +2]; String[] pkgs = new String[2 * tokens.size() +2]; int arrayIndex = 0; for (int i=tokens.size(); i>=0; i--) { StringBuffer path = new StringBuffer(); StringBuffer pkg = new StringBuffer(); for (int j=0; j<i; j++) { path.append("/").append((String)tokens.get(j)); pkg.append((String)tokens.get(j)).append('.'); } paths[arrayIndex] = path.append("/").append(fileName).toString(); pkgs[arrayIndex] = pkg.append("/").append(className).toString(); arrayIndex++; } for (int i=tokens.size(); i>=0; i--) { StringBuffer path = new StringBuffer(); StringBuffer pkg = new StringBuffer(); for (int j=0; j<i; j++) { path.append("/").append((String)tokens.get(j)); pkg.append((String)tokens.get(j)).append('.'); } paths[arrayIndex] = path.append(defaultLayoutTemplate).toString(); pkgs[arrayIndex] = pkg.append("Default").toString(); arrayIndex++; } if ( logger.isDebugEnabled() ) { for (int i=0; i<paths.length; i++) { logger.debug("JetspeedTemplateService.parseScreen: paths[" + i + "] = " + paths[i]); } } String[] holder = new String[3]; holder[0] = getScreenName(pkgs); holder[1] = getLayoutTemplateName(pathRoot, paths); holder[2] = template; return holder; } /** * Parse the template name out to a package path to locate the * Navigation module. This is different than the Screen/Layout * parser in that it only looks for packages. Note: If caching is * enabled, this is only performed once for each unique template. * * @param String The template name (i.e folder/headernav.wm). * @return A String with the name of the Navigation module to use * for the template. */ protected String parseNavigationTemplate( String template ) { StringTokenizer st = new StringTokenizer(template, "/"); List tokens = new ArrayList(st.countTokens()); while(st.hasMoreTokens()) { String token = st.nextToken(); if (!token.equals("")) { tokens.add(token); } } String fileName = (String)tokens.get(tokens.size() - 1); tokens.remove(tokens.size() - 1); int dot = fileName.lastIndexOf('.'); String className = null; if (dot>0) { className = fileName.substring(0, dot); } else { className = fileName; } String firstChar = String.valueOf(className.charAt(0)); firstChar = firstChar.toUpperCase(); className = firstChar + className.substring(1); String[] pkgs = new String[tokens.size() + 2]; int arrayIndex = 0; for (int i=tokens.size(); i>=0; i--) { StringBuffer pkg = new StringBuffer(); for (int j=0; j<i; j++) { pkg.append((String)tokens.get(j)).append('.'); } if ( i == tokens.size() ) { StringBuffer distinctPkg = new StringBuffer(pkg.toString()); pkgs[arrayIndex] = distinctPkg.append(className).toString(); arrayIndex++; } pkgs[arrayIndex] = pkg.append("Default").toString(); arrayIndex++; } return getNavigationName( pkgs); } /** * Extract possible layouts paths. * * @param possiblePaths A String[] with possible paths to search. * @return A String with the name of the layout template. */ private String getLayoutTemplateName(String pathRoot, String[] possiblePaths) { if ( logger.isDebugEnabled() ) { logger.debug("JetspeedTemplatePage.getLayoutTemplateName: pathRoot " + pathRoot); for (int i=0; i<possiblePaths.length; i++) { logger.debug("JetspeedTemplatePage.getLayoutTemplateName: possiblePaths[" + i + "]=" + possiblePaths[i]); } } for (int i=0; i<possiblePaths.length; i++) { if (new File(pathRoot, "layouts" + possiblePaths[i]).exists()) { if ( logger.isDebugEnabled() ) { logger.debug("JetspeedTemplatePage.getLayoutTemplateName: " + pathRoot + "/layouts" + possiblePaths[i] + " found."); } return possiblePaths[i]; } else { if ( logger.isDebugEnabled() ) { logger.debug("JetspeedTemplatePage.getLayoutTemplateName: " + pathRoot + "/layouts" + possiblePaths[i] + " NOT found."); } } } return defaultLayoutTemplate; } /** * Extract a possible Screen from the packages. * * @param possibleScreens A String[] with possible paths to * search. * @return A String with the name of the Screen class to use. */ private String getScreenName( String[] possibleScreens) { for (int i=0; i<possibleScreens.length; i++) { try { ScreenLoader.getInstance().getInstance(possibleScreens[i]); return possibleScreens[i]; } catch (Exception e) { logger.error( "Exception in getScreenName", e ); } } return defaultScreen; } /** * Seaches for the Navigation class that may match the * name of the Navigation template. * * @param possibleNavigations A String[] with possible navigation * packages. * @return A String with the name of the Navigation class to use. */ private String getNavigationName( String[] possibleNavigations) { for (int i=0; i<possibleNavigations.length; i++) { try { NavigationLoader.getInstance().getInstance(possibleNavigations[i]); return possibleNavigations[i]; } catch (Exception e) { logger.error( "Exception in getNavigationName", e ); } } return defaultNavigation; } }