/*
* Copyright 2000-2001,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.
*/
package org.apache.jetspeed.services.template;
// java.io
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.ServletConfig;
import org.apache.commons.configuration.Configuration;
import org.apache.jetspeed.capability.CapabilityMap;
import org.apache.jetspeed.services.Profiler;
import org.apache.jetspeed.services.customlocalization.CustomLocalizationService;
import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
import org.apache.jetspeed.services.logging.JetspeedLogger;
import org.apache.jetspeed.services.resources.JetspeedResources;
import org.apache.jetspeed.services.rundata.JetspeedRunData;
import org.apache.jetspeed.util.ServiceUtil;
import org.apache.turbine.services.InitializationException;
import org.apache.turbine.services.TurbineBaseService;
import org.apache.turbine.services.TurbineServices;
import org.apache.turbine.services.jsp.JspService;
import org.apache.turbine.services.localization.LocalizationService;
import org.apache.turbine.services.resources.TurbineResources;
import org.apache.turbine.services.servlet.TurbineServlet;
import org.apache.turbine.services.template.TurbineTemplate;
import org.apache.turbine.services.velocity.VelocityService;
import org.apache.turbine.util.RunData;
/**
* <p>
* Implements all template location related operations. Template location
* algorithms are different from the Velocity template location, since Jetspeed
* has a specialized template directory structure. This is a fix to get us
* through unti the TurbineTemplateService can locate resources by NLS and
* mediatype. Then it can be removed
* </p>
*
* <p>
* The directory structure is currently layout out in the following order:
* /templateType/mediaType/LanguageCode/CountryCode
* </p>
* <p>
* Example: /screens/html/en/US/resource.vm
* </p>
*
* @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
* @author <a href="mailto:rapahel@apache.org">Raphael Luta</a>
* @author <a href="mailto:paulsp@apache.org">Paul Spener</a>
* @author <a href="mailto:kimptoc_mail@yahoo.com">Chris Kimpton</a>
* @author <a href="mailto:weaver@apache.org">Scott T. Weaver</a>
* @version $Id: JetspeedTemplateLocatorService.java,v 1.22 2004/02/23 03:38:54
* jford Exp $
*/
public class JetspeedTemplateLocatorService extends TurbineBaseService
implements TemplateLocatorService {
/**
* Static initialization of the logger for this class
*/
private static final JetspeedLogger logger = JetspeedLogFactoryService
.getLogger(JetspeedTemplateLocatorService.class.getName());
private final static String CONFIG_TEMPLATE_ROOT = ".templateRoot";
private final static String CONFIG_PORTLET_GLOBAL_SEARCH =
".portlet.global.search";
private final static String CONFIG_HOT_DEPLOY = ".hot.deploy";
private final static String DIR_SCREENS = "/screens";
private final static String DIR_LAYOUTS = "/layouts";
private final static String DIR_PORTLETS = "/portlets";
private final static String DIR_CONTROLS = "/controls";
private final static String DIR_CONTROLLERS = "/controllers";
private final static String DIR_NAVIGATIONS = "/navigations";
private final static String DIR_PARAMETERS = "/parameters";
private final static String DIR_EMAILS = "/emails";
private static final String PATH_SEPARATOR = "/";
// messages
private final static String MSG_MISSING_PARAMETER =
"JetspeedTemplateLocatorService initialization failed. Missing parameter:";
// Template Service Constants
private static final String TEMPLATE_EXTENSION = "template.extension";
private static final String DEFAULT_LAYOUT = "default.layout.template";
// Template services
private static VelocityService velocityService;
private static JspService jspService;
// the template root directories, webapp relative
private String[] templateRoots;
// check the file system if template not found in name cache
private boolean hotDeploy = false;
// template name cache
private Map templateMap = null;
// include screens when searching for portlet template
private boolean useGlobalPortletSearch = false;
/**
* This is the early initialization method called by the Turbine
* <code>Service</code> framework
*
* @param conf
* The <code>ServletConfig</code>
* @exception throws a <code>InitializationException</code> if the service
* fails to initialize
*/
@Override
public synchronized void init(ServletConfig conf)
throws InitializationException {
// already initialized
if (getInit()) {
return;
}
initConfiguration();
// initialization done
setInit(true);
}
@Override
public void init() throws InitializationException {
logger.info("Late init for JetspeedTemplateLocatorService called");
while (!getInit()) {
// Not yet...
try {
Thread.sleep(100);
logger.info("Waiting for init of JetspeedTemplateLocatorService...");
} catch (InterruptedException ie) {
logger.error("Exception", ie);
}
}
}
/**
* This is the shutdown method called by the Turbine <code>Service</code>
* framework
*/
@Override
public void shutdown() {
}
/**
* Locate a screen template using Jetspeed template location algorithm,
* searching by mediatype and language criteria extracted from the request
* state in rundata.
*
* @param data
* The rundata for the request.
* @param template
* The name of the template.
*
* @return The path relative to the screens directory for the requested screen
* template, or null if not found.
*/
@Override
public String locateScreenTemplate(RunData data, String template) {
List templatePaths = localizeTemplateName(data);
Iterator i = templatePaths.iterator();
String located = null;
while (i.hasNext()) {
String path = (String) i.next();
located = locateTemplate(data, DIR_SCREENS, path, template);
if (null != located) {
return located;
}
}
if (null == located) {
// we have not found the requested sreen but still need to return
// something, search for the default screen
i = templatePaths.iterator();
template = "/default." + getTemplateExtension(template);
while (i.hasNext()) {
String path = (String) i.next();
located = locateTemplate(data, DIR_SCREENS, path, template);
if (null != located) {
return located;
}
}
}
return located;
}
/**
* Locate a layout template using Jetspeed template location algorithm,
* searching by mediatype and language criteria extracted from the request
* state in rundata.
*
* @param data
* The rundata for the request.
* @param template
* The name of the template.
*
* @return The path relative to the layouts directory for the requested layout
* template, or null if not found.
*/
@Override
public String locateLayoutTemplate(RunData data, String template) {
List templatePaths = localizeTemplateName(data);
Iterator i = templatePaths.iterator();
String located = null;
while (i.hasNext()) {
String path = (String) i.next();
located = locateTemplate(data, DIR_LAYOUTS, path, template);
if (null != located) {
return located;
}
}
if (null == located) {
// we have not found the requested layout but still need to return
// something, search for the default layout
i = templatePaths.iterator();
// template = "/default." + getTemplateExtension(template);
template = getTemplateLayout(getTemplateExtension(template));
while (i.hasNext()) {
String path = (String) i.next();
located = locateTemplate(data, DIR_LAYOUTS, path, template);
if (null != located) {
return located;
}
}
}
return located;
}
/**
* Locate a controller template using Jetspeed template location algorithm,
* searching by mediatype and language criteria extracted from the request
* state in rundata.
*
* @param data
* The rundata for the request.
* @param template
* The name of the template.
*
* @return The path relative to the controllers directory for the requested
* controller template, or null if not found.
*/
@Override
public String locateNavigationTemplate(RunData data, String template) {
List templatePaths = localizeTemplateName(data);
Iterator i = templatePaths.iterator();
while (i.hasNext()) {
String path = (String) i.next();
String located = locateTemplate(data, DIR_NAVIGATIONS, path, template);
if (null != located) {
return located;
}
}
return null;
}
/**
* Locate a portlet template using Jetspeed template location algorithm,
* searching by mediatype and language criteria extracted from the request
* state in rundata.
*
* @param data
* The rundata for the request.
* @param template
* The name of the template.
*
* @return The path relative to the portlets directory for the requested
* portlet template, or null if not found.
*/
@Override
public String locatePortletTemplate(RunData data, String template) {
List templatePaths = localizeTemplateName(data);
Iterator i = templatePaths.iterator();
while (i.hasNext()) {
String path = (String) i.next();
String located = locateTemplate(data, DIR_PORTLETS, path, template);
if (null != located) {
return DIR_PORTLETS + located;
}
}
// Use "wide" search when required
if (useGlobalPortletSearch == true) {
String located = locateScreenTemplate(data, template);
if (located != null) {
return DIR_SCREENS + located;
}
}
return null;
}
/**
* Locate a control template using Jetspeed template location algorithm,
* searching by mediatype and language criteria extracted from the request
* state in rundata.
*
* @param data
* The rundata for the request.
* @param template
* The name of the template.
*
* @return The path relative to the controls directory for the requested
* control template, or null if not found.
*/
@Override
public String locateControlTemplate(RunData data, String template) {
List templatePaths = localizeTemplateName(data);
Iterator i = templatePaths.iterator();
while (i.hasNext()) {
String path = (String) i.next();
String located = locateTemplate(data, DIR_CONTROLS, path, template);
if (null != located) {
return DIR_CONTROLS + located;
}
}
return null;
}
/**
* Locate a controller template using Jetspeed template location algorithm,
* searching by mediatype and language criteria extracted from the request
* state in rundata.
*
* @param data
* The rundata for the request.
* @param template
* The name of the template.
*
* @return The path relative to the controllers directory for the requested
* controller template, or null if not found.
*/
@Override
public String locateControllerTemplate(RunData data, String template) {
List templatePaths = localizeTemplateName(data);
Iterator i = templatePaths.iterator();
while (i.hasNext()) {
String path = (String) i.next();
String located = locateTemplate(data, DIR_CONTROLLERS, path, template);
if (null != located) {
return DIR_CONTROLLERS + located;
}
}
return null;
}
/**
* Locate an email template using Jetspeed template location algorithm,
* searching by mediatype and language criteria extracted from the request
* state in rundata.
*
* @param data
* The rundata for the request.
* @param template
* The name of the template.
*
* @return The path relative to the emails directory for the requested email
* template, or null if not found.
*/
@Override
public String locateEmailTemplate(RunData data, String template) {
CustomLocalizationService locService =
(CustomLocalizationService) ServiceUtil
.getServiceByName(LocalizationService.SERVICE_NAME);
return locateEmailTemplate(data, template, locService.getLocale(data));
}
/**
* Locate an email template using Jetspeed template location algorithm,
* searching by mediatype and language.
*
* @param data
* The rundata for the request.
* @param template
* The name of the template.
* @param locale
* The name of the language.
*
* @return The path relative to the emails directory for the requested email
* template, or null if not found.
*/
@Override
public String locateEmailTemplate(RunData data, String template, Locale locale) {
List templatePaths = localizeTemplateName(data, locale);
Iterator i = templatePaths.iterator();
while (i.hasNext()) {
String path = (String) i.next();
String located = locateTemplate(data, DIR_EMAILS, path, template);
if (null != located) {
return DIR_EMAILS + located;
}
}
return null;
}
/**
* Locate a parameter style template using Jetspeed template location
* algorithm, searching by mediatype and language criteria extracted from the
* request state in rundata.
*
* @param data
* The rundata for the request.
* @param template
* The name of the template.
*
* @return The path relative to the portlets directory for the requested
* portlet template, or null if not found.
*/
@Override
public String locateParameterTemplate(RunData data, String template) {
List templatePaths = localizeTemplateName(data);
Iterator i = templatePaths.iterator();
while (i.hasNext()) {
String path = (String) i.next();
String located = locateTemplate(data, DIR_PARAMETERS, path, template);
if (null != located) {
return DIR_PARAMETERS + located;
}
}
return null;
}
/**
* General template location algorithm. Starts with the most specific
* resource, including mediatype + nls specification, and fallsback to least
* specific.
*
* @param data
* The rundata for the request.
* @param resourceType
* The path specific to the resource type sought (eg /screens).
* @param path
* The fullest path to the template based on simple NLS/mediatype
* directory.
* @param template
* The name of the template.
*
* @return the exact path to the template, or null if not found.
*/
private String locateTemplate(RunData data, String resourceType, String path,
String template) {
String located = null;
// Iterate through each of the template roots
for (int i = 0; i < templateRoots.length; i++) {
located =
locateTemplate(data, resourceType, path, template, templateRoots[i]);
if (located != null) {
break;
}
}
return located;
}
/**
* General template location algorithm. Starts with the most specific
* resource, including mediatype + nls specification, and fallsback to least
* specific.
*
* @param data
* The rundata for the request.
* @param resourceType
* The path specific to the resource type sought (eg /screens).
* @param path
* The fullest path to the template based on simple NLS/mediatype
* directory.
* @param template
* The name of the template.
* @param templateRoot
* The template root to be searched.
*
* @return the exact path to the template, or null if not found.
*/
private String locateTemplate(RunData data, String resourceType, String path,
String template, String templateRoot) {
String finalPath;
// make sure resourceType doesn't end with "/" but starts with "/"
if (resourceType.endsWith(PATH_SEPARATOR)) {
resourceType = resourceType.substring(0, resourceType.length() - 1);
}
if (!resourceType.startsWith(PATH_SEPARATOR)) {
resourceType = PATH_SEPARATOR + resourceType;
}
// make sure path doesn't end with "/" but starts with "/"
if (path.endsWith(PATH_SEPARATOR)) {
path = path.substring(0, path.length() - 1);
}
if (!path.startsWith(PATH_SEPARATOR)) {
path = PATH_SEPARATOR + path;
}
// make sure template starts with "/"
if (!template.startsWith(PATH_SEPARATOR)) {
template = PATH_SEPARATOR + template;
}
StringBuffer fullPath = new StringBuffer(templateRoot);
if (!templateRoot.endsWith(PATH_SEPARATOR)) {
fullPath.append(PATH_SEPARATOR);
}
fullPath.append(getTemplateExtension(template));
fullPath.append(resourceType);
String basePath = fullPath.toString();
String realPath = null;
String workingPath = null;
do {
workingPath = path + template;
realPath = TurbineServlet.getRealPath(basePath + workingPath);
// the current template exists in cache, return the corresponding path
if (templateExists(realPath, true)) {
if (logger.isDebugEnabled()) {
logger.debug("TemplateLocator: template exists in cache: "
+ realPath
+ " returning "
+ workingPath);
}
return workingPath;
} else if (this.hotDeploy == true) {
// Try to locate it directly on file system, perhaps it was recently
// added
if (templateExists(realPath, false)) {
if (logger.isDebugEnabled()) {
logger
.debug("TemplateLocator: template exists on the file system: "
+ realPath
+ " returning "
+ workingPath);
}
// add it to the map
// templateMap.put(workingPath, null);
templateMap.put(realPath, null);
return workingPath;
}
}
// else strip path of one of its components and loop
int pt = path.lastIndexOf(PATH_SEPARATOR);
if (pt > -1) {
path = path.substring(0, pt);
} else {
path = null;
}
} while (path != null);
return null;
}
/**
* Helper function for template locator to find a localized (NLS) resource.
* Considers both language and country resources as well as all the possible
* media-types for the request
*
* @param data
* The rundata for the request.
*
* @return The possible paths to a localized template ordered by descending
* preference
*/
private List localizeTemplateName(RunData data) {
return localizeTemplateName(data, null);
}
/**
* Helper function for template locator to find a localized (NLS) resource.
* Considers both language and country resources as well as all the possible
* media-types for the request
*
* @param data
* The rundata for the request.
* @param locale
* The locale for the request.
*
* @return The possible paths to a localized template ordered by descending
* preference
*/
private List localizeTemplateName(RunData data, Locale inLocale) {
List templates = new ArrayList();
Locale tmplocale = null;
if (inLocale != null) {
tmplocale = inLocale;
} else {
CustomLocalizationService locService =
(CustomLocalizationService) ServiceUtil
.getServiceByName(LocalizationService.SERVICE_NAME);
tmplocale = locService.getLocale(data);
}
// Get the locale store it in the user object
if (tmplocale == null) {
tmplocale =
new Locale(
TurbineResources.getString("locale.default.language", "en"),
TurbineResources.getString("locale.default.country", "US"));
}
if (data.getUser() != null) {
data.getUser().setTemp("locale", tmplocale);
}
StringBuffer templatePath = new StringBuffer();
// retrieve all the possible media types
String type =
data.getParameters().getString(Profiler.PARAM_MEDIA_TYPE, null);
List types = new ArrayList();
CapabilityMap cm = ((JetspeedRunData) data).getCapability();
// Grab the Locale from the temporary storage in the User object
Locale locale =
data.getUser() != null
? (Locale) data.getUser().getTemp("locale")
: tmplocale;
String language = locale.getLanguage();
String country = locale.getCountry();
if (null != type) {
types.add(type);
} else {
Iterator i = cm.listMediaTypes();
while (i.hasNext()) {
types.add(i.next());
}
}
Iterator typeIterator = types.iterator();
while (typeIterator.hasNext()) {
type = (String) typeIterator.next();
if ((type != null) && (type.length() > 0)) {
templatePath.append(PATH_SEPARATOR).append(type);
}
if ((language != null) && (language.length() > 0)) {
templatePath.append(PATH_SEPARATOR).append(language);
}
if ((country != null) && (country.length() > 0)) {
templatePath.append(PATH_SEPARATOR).append(country);
}
templates.add(templatePath.toString());
templatePath.setLength(0);
}
return templates;
}
/**
* Returns the extension for the specified template
*
* @param template
* the template name to scan for an extension
* @return the template extension if it exists or the default template
* extension
*/
private String getTemplateExtension(String template) {
String ext = TurbineTemplate.getDefaultExtension();
int idx = template.lastIndexOf(".");
if (idx > 0) {
ext = template.substring(idx + 1);
}
return ext;
}
/**
* Checks for the existence of a template resource given a key. The key are
* absolute paths to the templates, and are cached in a template cache for
* performance.
*
* @param key
* The absolute path to the template resource.
*
* @return True when the template is found, otherwise false.
*/
public boolean templateExists(String templateKey, boolean useNameCache) {
if (null == templateKey) {
return false;
}
if (useNameCache == true) {
return templateMap.containsKey(templateKey);
}
return (new File(templateKey).exists());
}
/**
* Loads the configuration parameters for this service from the
* JetspeedResources.properties file.
*
* @exception throws a <code>InitializationException</code> if the service
* fails to initialize
*/
private void initConfiguration() throws InitializationException {
templateRoots =
JetspeedResources.getStringArray(TurbineServices.SERVICE_PREFIX
+ TemplateLocatorService.SERVICE_NAME
+ CONFIG_TEMPLATE_ROOT);
if ((templateRoots == null) || (templateRoots.length == 0)) {
throw new InitializationException(MSG_MISSING_PARAMETER
+ CONFIG_TEMPLATE_ROOT);
}
templateMap = new HashMap();
for (int i = 0; i < templateRoots.length; i++) {
String templateRoot = templateRoots[i];
if (!templateRoot.endsWith(PATH_SEPARATOR)) {
templateRoot = templateRoot + PATH_SEPARATOR;
}
if (logger.isDebugEnabled()) {
logger.debug("TemplateLocator: Adding templateRoot:" + templateRoot);
}
// traverse starting from the root template directory and add resources
String templateRootPath = TurbineServlet.getRealPath(templateRoot);
if (null != templateRootPath) {
loadNameCache(templateRootPath, "");
}
}
velocityService =
(VelocityService) TurbineServices.getInstance().getService(
VelocityService.SERVICE_NAME);
jspService =
(JspService) TurbineServices.getInstance().getService(
JspService.SERVICE_NAME);
useGlobalPortletSearch =
JetspeedResources.getBoolean(TurbineServices.SERVICE_PREFIX
+ TemplateLocatorService.SERVICE_NAME
+ CONFIG_PORTLET_GLOBAL_SEARCH, false);
hotDeploy =
JetspeedResources.getBoolean(TurbineServices.SERVICE_PREFIX
+ TemplateLocatorService.SERVICE_NAME
+ CONFIG_HOT_DEPLOY, true);
}
/**
* Loads the template name cache map to accelerate template searches.
*
* @param path
* The template
* @param name
* just the name of the resource
*/
private void loadNameCache(String path, String name) {
File file = new File(path);
if (file.isFile()) {
// add it to the map
templateMap.put(path, null);
} else {
if (file.isDirectory()) {
if (!path.endsWith(File.separator)) {
path += File.separator;
}
String list[] = file.list();
// Process all files recursivly
for (int ix = 0; list != null && ix < list.length; ix++) {
loadNameCache(path + list[ix], list[ix]);
}
}
}
}
/**
* Correctly locate the default layout based on the default.layout.template
* property of the appropriate template service.
*
* @author <a href="mailto:sweaver@rippe.com">Scott Weaver</a>
*/
private String getTemplateLayout(String extension) {
String dftLayout = "/default." + extension;
Configuration velocityCfg = null;
Configuration jspCfg = null;
if (velocityService != null) {
velocityCfg = velocityService.getConfiguration();
}
if (jspService != null) {
jspCfg = jspService.getConfiguration();
}
if (velocityCfg != null
&& velocityCfg.getString(TEMPLATE_EXTENSION).indexOf(extension) > -1) {
return velocityCfg.getString(DEFAULT_LAYOUT, dftLayout);
} else if (jspCfg != null
&& jspCfg.getString(TEMPLATE_EXTENSION).indexOf(extension) > -1) {
return jspCfg.getString(DEFAULT_LAYOUT, dftLayout);
} else {
return dftLayout;
}
}
}