/*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (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.mozilla.org/MPL/>.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
 * language governing rights and limitations under the License.
 * 
 * The Original Code is the Venice Web Communities System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.venice.servlets.format;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.xml.parsers.*;
import org.apache.log4j.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import com.silverwrist.util.DOMElementHelper;
import com.silverwrist.util.IOUtil;
import com.silverwrist.util.StringUtil;
import com.silverwrist.venice.core.UserContext;
import com.silverwrist.venice.core.VeniceEngine;
import com.silverwrist.venice.except.AccessError;
import com.silverwrist.venice.except.ConfigException;
import com.silverwrist.venice.except.DataException;
import com.silverwrist.venice.servlets.Variables;
import com.silverwrist.venice.servlets.format.menus.LeftMenu;
import com.silverwrist.venice.servlets.format.sideboxes.SideBoxFactory;

public class RenderConfig implements ColorSelectors
{
  /*--------------------------------------------------------------------------------
   * Static data values
   *--------------------------------------------------------------------------------
   */

  protected static final String ATTR_NAME = "com.silverwrist.venice.servlets.RenderConfig";
  protected static final String CONFIG_FILE_PARAM = "render.config";

  private static Category logger = Category.getInstance(RenderConfig.class);

  private static Map colornames_map;

  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private Document config;
  private String site_title;
  private boolean want_comments;
  private boolean allow_gzip;
  private boolean no_smart_tags;
  private String font_face;
  private File stylesheet;
  private long stylesheet_time = 0;
  private String image_url;
  private String static_url;
  private String site_logo;
  private int site_logo_width = 140;
  private int site_logo_height = 80;
  private String site_logo_linkURL = null;
  private HashMap stock_messages;
  private HashMap menus;
  private String[] colors_array;
  private int footer_logo_scale;
  private Map sidebox_factories;
  private String photo_not_avail;
  private boolean photo_not_avail_fixup;

  /*--------------------------------------------------------------------------------
   * Constructor
   *--------------------------------------------------------------------------------
   */

  protected RenderConfig(String config_file, String root_file_path) throws ConfigException
  {
    config = loadConfiguration(config_file);

    // Make sure the configuration is valid...
    Element root = config.getDocumentElement();
    if (!(root.getTagName().equals("render-config")))
    { // not the correct root tag name
      logger.fatal("config document is not a <render-config/> document (root tag: <"
		   + root.getTagName() + "/>)");
      throw new ConfigException("document is not a <render-config/> document",root);

    } // end if

    // Get the site name.
    DOMElementHelper root_h = new DOMElementHelper(root);
    site_title = root_h.getSubElementText("site-name");
    if (site_title==null)
    { // no <site-name/> section - bail out now!
      logger.fatal("config document has no <site-title/> element");
      throw new ConfigException("no <site-title/> section found in config file",root);

    } // end if

    if (logger.isDebugEnabled())
      logger.debug("Site title: " + site_title);

    Element render_sect = root_h.getSubElement("rendering");
    if (render_sect==null)
    { // no <rendering/> section - bail out now!
      logger.fatal("config document has no <rendering/> section");
      throw new ConfigException("no <rendering/> section found in config file",root);

    } // end if

    DOMElementHelper render_sect_h = new DOMElementHelper(render_sect);
    want_comments = render_sect_h.hasChildElement("html-comments");
    allow_gzip = render_sect_h.hasChildElement("gzip-output");
    no_smart_tags = !(render_sect_h.hasChildElement("ms-copyright-violations"));
    if (logger.isDebugEnabled())
    { // log the read values
      logger.debug("Use HTML comments: " + String.valueOf(want_comments));
      logger.debug("Use GZIP encoding: " + String.valueOf(allow_gzip));
      logger.debug("Disable IE Smart Tags: " + String.valueOf(no_smart_tags));

    } // end if

    font_face = render_sect_h.getSubElementText("font");
    if (font_face==null)
    { // no <font/> tag - bail out now!
      logger.fatal("<rendering/> section has no <font/> element");
      throw new ConfigException("no <font/> found in <rendering/> section",render_sect);

    } // end if

    if (logger.isDebugEnabled())
      logger.debug("Font face: " + font_face);

    String stylesheet_loc = render_sect_h.getSubElementText("stylesheet");
    if (stylesheet_loc!=null)
    { // we're using Cascading Stylesheets - load it and test for existence
      if (!(stylesheet_loc.startsWith("/")))
	stylesheet_loc = root_file_path + stylesheet_loc;
      if (logger.isDebugEnabled())
	logger.debug("Stylesheet location: " + stylesheet_loc);

      // Test to make sure the stylesheet is actually present.
      stylesheet = new File(stylesheet_loc);
      if (!(stylesheet.exists() && stylesheet.canRead()))
      { // it's not there - bail out!
	logger.fatal("unable to read stylesheet file: " + stylesheet_loc);
	throw new ConfigException("stylesheet " + stylesheet_loc + " cannot be read",render_sect);

      } // end if

    } // end if
    else  // no stylesheet
      stylesheet = null;

    Element colors_sect = render_sect_h.getSubElement("colors");
    if (colors_sect==null)
    { // no <colors/> tag - bail out now!
      logger.fatal("<rendering/> section has no <colors/> element");
      throw new ConfigException("no <colors/> found in <rendering/> section",render_sect);

    } // end if

    colors_array = new String[colornames_map.size()];
    int i;
    NodeList colors_nlist = colors_sect.getChildNodes();
    for (i=0; i<colors_nlist.getLength(); i++)
    { // look at all subelements and map their names to color selectors
      Node foo = colors_nlist.item(i);
      if (foo.getNodeType()==Node.ELEMENT_NODE)
      { // map the element name to a color selector
	Integer csel = (Integer)(colornames_map.get(foo.getNodeName()));
	if (csel!=null)
	{ // load the specified color into the colors array
	  DOMElementHelper foo_h = new DOMElementHelper((Element)foo);
	  colors_array[csel.intValue()] = foo_h.getElementText();

	} // end if

      } // end if

    } // end for

    try
    { // load in the footer logo scale
      String tmp = render_sect_h.getSubElementText("footer-logo-scale");
      if (tmp==null)
	footer_logo_scale = 100;
      else
	footer_logo_scale = Integer.parseInt(tmp.trim());

    } // end try
    catch (NumberFormatException nfe)
    { // just default on serious error
      footer_logo_scale = 100;

    } // end catch

    Element paths_sect = root_h.getSubElement("paths");
    if (paths_sect==null)
    { // no <paths/> section - bail out now!
      logger.fatal("config document has no <paths/> section");
      throw new ConfigException("no <paths/> section found in config file",root);

    } // end if

    DOMElementHelper paths_sect_h = new DOMElementHelper(paths_sect);
    image_url = paths_sect_h.getSubElementText("image");
    if (image_url==null)
    { // no <image/> tag - bail out now!
      logger.fatal("<paths/> section has no <image/> element");
      throw new ConfigException("no <image/> found in <paths/> section",paths_sect);

    } // end if

    if (logger.isDebugEnabled())
      logger.debug("Image path: " + image_url);

    static_url = paths_sect_h.getSubElementText("static");
    if (static_url==null)
    { // no <static/> tag - bail out now!
      logger.fatal("<paths/> section has no <static/> element");
      throw new ConfigException("no <static/> found in <paths/> section",paths_sect);

    } // end if

    if (logger.isDebugEnabled())
      logger.debug("Static files path: " + static_url);

    Element site_logo_elt = paths_sect_h.getSubElement("site-logo");
    if (site_logo_elt==null)
    { // no <site-logo/> tag - bail out now!
      logger.fatal("<paths/> section has no <site-logo/> element");
      throw new ConfigException("no <site-logo/> found in <paths/> section",paths_sect);

    } // end if

    DOMElementHelper site_logo_h = new DOMElementHelper(site_logo_elt);
    site_logo = site_logo_h.getElementText();
    if (site_logo==null)
    { // no site logo specified - bail out now!
      logger.fatal("<paths/> section has no site logo element");
      throw new ConfigException("no site logo found in <paths/> section",paths_sect);

    } // end if

    // get logo width and height
    Integer fooint = site_logo_h.getAttributeInt("width");
    if (fooint!=null)
      site_logo_width = fooint.intValue();
    fooint = site_logo_h.getAttributeInt("height");
    if (fooint!=null)
      site_logo_height = fooint.intValue();

    // get logo link URL
    String tmp = site_logo_elt.getAttribute("href");
    if (!(StringUtil.isStringEmpty(tmp)))
      site_logo_linkURL = tmp;

    if (logger.isDebugEnabled())
      logger.debug("Site logo: " + site_logo);

    // Load the sidebox configuration information.
    String sidebox_config = paths_sect_h.getSubElementText("sidebox-config");
    if (sidebox_config==null)
    { // the sidebox config was not present
      logger.fatal("<paths/> section has no <sidebox-config/> element");
      throw new ConfigException("no <sidebox-config/> found in <paths/> section",paths_sect);

    } // end if

    if (!(sidebox_config.startsWith("/")))
      sidebox_config = root_file_path + sidebox_config;
    Document sb_doc = loadConfiguration(sidebox_config);
    Element sb_root = sb_doc.getDocumentElement();
    if (!(sb_root.getTagName().equals("sidebox-config")))
    { // the sidebox configuration file isn't the right type
      logger.fatal("config document is not a <sidebox-config/> document (root tag: <"
		   + sb_root.getTagName() + "/>)");
      throw new ConfigException("document is not a <sidebox-config/> document",sb_root);

    } // end if

    NodeList sb_nodes = sb_root.getChildNodes();
    HashMap tmp_factories = new HashMap();
    for (i=0; i<sb_nodes.getLength(); i++)
    { // extract each sidebox section from the list and build a new master sidebox object
      Node s = sb_nodes.item(i);
      if ((s.getNodeType()==Node.ELEMENT_NODE) && (s.getNodeName().equals("sidebox")))
      { // get the various attributes of the sidebox
	Element sb = (Element)s;
	try
	{ // get the ID of the sidebox first
	  tmp = sb.getAttribute("id");
	  if (StringUtil.isStringEmpty(tmp))
	  { // there was no ID specified for the sidebox
	    logger.fatal("<sidebox/> specified with no ID!");
	    throw new ConfigException("no ID specified in <sidebox/>",sb);

	  } // end if

	  // convert to an integer which we'll be using later in the HashMap
	  Integer id = new Integer(tmp);

	  // get the name of the factory class
	  DOMElementHelper sb_h = new DOMElementHelper(sb);
	  tmp = sb_h.getSubElementText("factory-class");
	  if (StringUtil.isStringEmpty(tmp))
	  { // the factory class was not specified!
	    logger.fatal("<sidebox/> config incomplete (missing <factory-class/>)!");
	    throw new ConfigException("configuration of <sidebox/> is not complete!",sb);

	  } // end if

	  Class factory_class = Class.forName(tmp);
	  SideBoxFactory factory = (SideBoxFactory)(factory_class.newInstance());
	  factory.setConfiguration(sb);
	  tmp_factories.put(id,factory);

	} // end if
	catch (NumberFormatException e1)
	{ // ID value was not an integer
	  logger.fatal("<sidebox/> ID not numeric!");
	  throw new ConfigException("non-numeric ID specified in <sidebox/>",sb);

	} // end catch
	catch (ClassNotFoundException e2)
	{ // sidebox could not be configured
	  logger.fatal("<sidebox/> config <factory-class/> not valid!",e2);
	  throw new ConfigException("<factory-class/> in <sidebox/> is not a valid class!",sb);

	} // end catch
	catch (IllegalAccessException e3)
	{ // could not access class and/or constructor
	  logger.fatal("<sidebox/> <factory-class/> is not accessible!",e3);
	  throw new ConfigException("<factory-class/> in <sidebox/> is not accessible!",sb);

	} // end catch
	catch (InstantiationException e4)
	{ // unable to create the class
	  logger.fatal("<sidebox/> <factory-class/> could not be instantiated!",e4);
	  throw new ConfigException("<factory-class/> in <sidebox/> could not be created!",sb);

	} // end catch
	catch (ClassCastException e5)
	{ // the class is not a SideBoxFactory implementor - cannot create
	  logger.fatal("<sidebox/> <factory-class/> is not a SideBoxFactory!",e5);
	  throw new ConfigException("<factory-class/> in <sidebox/> is not the correct type!",sb);

	} // end catch
	
      } // end if

    } // end for

    sidebox_factories = Collections.unmodifiableMap(tmp_factories);

    Element pna = paths_sect_h.getSubElement("photo-not-avail");
    if (pna!=null)
    { // copy element text and fixup information
      DOMElementHelper pna_h = new DOMElementHelper(pna);
      photo_not_avail = pna_h.getElementText();
      photo_not_avail_fixup = pna_h.hasAttribute("fixup");

    } // end if
    else
    { // just load the defaults
      photo_not_avail = "photo_not_avail.gif";
      photo_not_avail_fixup = true;

    } // end else

    Element msg_sect = root_h.getSubElement("messages");
    if (msg_sect==null)
    { // no <messages/> section - bail out now!
      logger.fatal("config document has no <messages/> section");
      throw new ConfigException("no <messages/> section found in config file",root);

    } // end if

    // Initialize the stock messages list.
    stock_messages = new HashMap();
    NodeList msg_nodes = msg_sect.getChildNodes();
    for (i=0; i<msg_nodes.getLength(); i++)
    { // examine all subnodes to add them to the message text
      Node msgn = msg_nodes.item(i);
      if (msgn.getNodeType()==Node.ELEMENT_NODE)
      { // add it to the hash table by its tag name
	Element msgel = (Element)msgn;
	DOMElementHelper h = new DOMElementHelper(msgel);
	String txt = h.getElementText();
	if (txt!=null)
	  stock_messages.put(msgel.getTagName(),txt.trim());

      } // end if

    } // end for

    if (logger.isDebugEnabled())
      logger.debug(stock_messages.size() + " stock messages loaded from config");

    Element menu_sect = root_h.getSubElement("menu-definitions");
    if (menu_sect==null)
    { // no <menu-definitions/> section - bail out now!
      logger.fatal("config document has no <menu-definitions/> section");
      throw new ConfigException("no <menu-definitions/> section found in config file",root);

    } // end if

    // Initialize the menus list.
    menus = new HashMap();
    NodeList menu_nodes = menu_sect.getChildNodes();
    for (i=0; i<menu_nodes.getLength(); i++)
    { // look for <menudef> subnodes and use them to initialize menus
      Node mn = menu_nodes.item(i);
      if (mn.getNodeType()==Node.ELEMENT_NODE)
      { // found an element - now check it's name
	if (mn.getNodeName().equals("menudef"))
	{ // root of a menu definition - get its ID, build it, and save it
	  Element mel = (Element)mn;
	  String menuid = mel.getAttribute("id");
	  if (menuid==null)
	  { // no menu ID attribute
	    logger.fatal("<menudef/> seen with no \"id\" attribute");
	    throw new ConfigException("<menudef/> seen with no \"id\" attribute",mel);

	  } // end if

	  // create the menu and add it to the mapping
	  LeftMenu menu = new LeftMenu(mel,menuid);
	  menus.put(menuid,menu);
	  if (logger.isDebugEnabled())
	    logger.debug("menu \"" + menuid + "\" defined");

	} // end if (found the root of a menu definition)
	else
	{ // unknown element - bail out!
	  logger.fatal("config document has unknown node <" + mn.getNodeName() +
		       "/> inside <menu-definitions/>");
	  throw new ConfigException("unknown node name <" + mn.getNodeName() + "/> in <menu-definitions/>",
				    menu_sect);

	} // end else

      } // end if

    } // end for

    if (logger.isDebugEnabled())
      logger.debug(menus.size() + " menu definitions loaded from config");

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Internal functions
   *--------------------------------------------------------------------------------
   */

  private static Document loadConfiguration(String configname) throws ConfigException
  {
    try
    { // create a simple DOM parser by using the Java XML parsing API
      DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance();
      fac.setNamespaceAware(false);
      fac.setValidating(false);
      DocumentBuilder parser = fac.newDocumentBuilder();

      // access the config file and parse it into our config data tree
      File configfile = new File(configname);
      Document rc = parser.parse(configfile);
      if (logger.isDebugEnabled())
	logger.debug("configuration loaded successfully");
      return rc;

    } // end try
    catch (FactoryConfigurationError e1)
    { // if the document builder factory could not be created
      logger.fatal("Parser factory configuration error: " + e1.getMessage(),e1);
      throw new ConfigException("XML parser factory could not be created - " + e1.getMessage());

    } // end catch
    catch (ParserConfigurationException e2)
    { // if the XML parser itself could not be created
      logger.fatal("Parser configuration error: " + e2.getMessage(),e2);
      throw new ConfigException("XML parser could not be created - " + e2.getMessage(),e2);

    } // end catch
    catch (SAXException e3)
    { // if the XML parser choked on our document
      if (e3 instanceof SAXParseException)
      { // we have a detailed message - make a proper exception
	SAXParseException e3a = (SAXParseException)e3;
	logger.fatal("Config file error [" + configname + ":" + e3a.getLineNumber() + ","
		     + e3a.getColumnNumber() + "]: " + e3a.getMessage(),e3a);
	throw new ConfigException("Configuration file error: " + e3a.getMessage() + " at line "
				  + e3a.getLineNumber() + ", column " + e3a.getColumnNumber(),e3a);

      } // end if
      else
      { // generic exception - just send up a simple error message
	logger.fatal("Config file error [" + configname + "]: " + e3.getMessage(),e3);
	throw new ConfigException("Configuration file error - " + e3.getMessage(),e3);

      } // end else

    } // end catch
    catch (IOException e4)
    { // error reading the config file itself off the disk
      logger.fatal("IO error reading config: " + e4.getMessage(),e4);
      throw new ConfigException("unable to read config file \"" + configname + "\" - " + e4.getMessage(),e4);

    } // end catch

  } // end loadConfiguration

  /*--------------------------------------------------------------------------------
   * External operations usable only by RenderData
   *--------------------------------------------------------------------------------
   */

  boolean useHTMLComments()
  {
    return want_comments;

  } // end useHTMLComments

  boolean isGZIPAllowed()
  {
    return allow_gzip;

  } // end isGZIPAllowed

  boolean noSmartTags()
  {
    return no_smart_tags;

  } // end noSmartTags

  String getFullImagePath(String name)
  {
    StringBuffer buf = new StringBuffer();
    buf.append(image_url).append(name);
    return buf.toString();

  } // end getFullImagePath

  String getStaticFilePath(String name)
  {
    StringBuffer buf = new StringBuffer();
    buf.append(static_url).append(name);
    return buf.toString();

  } // end getStaticFilePath

  String getTitleTag(String specific)
  {
    StringBuffer buf = new StringBuffer();
    buf.append("<TITLE>").append(specific).append(" - ").append(site_title).append("</TITLE>");
    return buf.toString();

  } // end getTitleTag

  String getSiteImageTag(int hspace, int vspace)
  {
    StringBuffer buf = new StringBuffer();
    if (site_logo_linkURL!=null)
      buf.append("<A HREF=\"").append(site_logo_linkURL).append("\">");
    buf.append("<IMG SRC=\"").append(site_logo).append("\" ALT=\"").append(site_title);
    buf.append("\" WIDTH=").append(site_logo_width).append(" HEIGHT=").append(site_logo_height);
    buf.append(" BORDER=0");
    if (hspace>0)
      buf.append(" HSPACE=").append(hspace);
    if (vspace>0)
      buf.append(" VSPACE=").append(vspace);
    buf.append('>');
    if (site_logo_linkURL!=null)
      buf.append("</A>");
    return buf.toString();

  } // end getSiteImageTag

  String getStdFontTag(String color, int size)
  {
    StringBuffer buf = new StringBuffer("<FONT FACE=\"");
    buf.append(font_face).append("\" SIZE=").append(size);
    if (color!=null)
      buf.append(" COLOR=\"").append(color).append("\"");
    buf.append('>');
    return buf.toString();

  } // end getStdFontTag

  String getStdFontTag(int selector, int size)
  {
    if ((selector<0) || (selector>=colors_array.length))
      throw new IndexOutOfBoundsException("getStdColor(): invalid color selector value");
    return getStdFontTag(colors_array[selector],size);

  } // end getStdFontTag

  String getStdBaseFontTag(int size)
  {
    StringBuffer buf = new StringBuffer("<BASEFONT FACE=\"");
    buf.append(font_face).append("\" SIZE=").append(size).append('>');
    return buf.toString();

  } // end getStdBaseFontTag

  public String getRequiredBullet()
  {
    StringBuffer buf = new StringBuffer("<FONT FACE=\"");
    buf.append(font_face).append("\" COLOR=\"red\">*</FONT>");
    return buf.toString();

  } // end getRequiredBullet

  void writeContentHeader(Writer out, String primary, String secondary) throws IOException
  {
    out.write("<SPAN CLASS=\"chead1\">" + getStdFontTag(colors_array[CONTENT_HEADER],5) + "<B>"
	      + StringUtil.encodeHTML(primary) + "</B></FONT></SPAN>");
    if (secondary!=null)
      out.write("&nbsp;&nbsp;<SPAN CLASS=\"chead2\">" + getStdFontTag(colors_array[CONTENT_HEADER],3) + "<B>"
		+ StringUtil.encodeHTML(secondary) + "</B></FONT></SPAN>");
    out.write("<HR ALIGN=LEFT SIZE=2 WIDTH=\"90%\" NOSHADE>\n");

  } // end writeContentHeader

  String getStockMessage(String identifier)
  {
    return (String)(stock_messages.get(identifier));

  } // end getStockMessage

  void writeStockMessage(Writer out, String identifier) throws IOException
  {
    String text = (String)(stock_messages.get(identifier));
    out.write(StringUtil.encodeHTML(text));

  } // end writeStockMessage

  String getStdColor(int selector)
  {
    if ((selector<0) || (selector>=colors_array.length))
      throw new IndexOutOfBoundsException("getStdColor(): invalid color selector value");
    return colors_array[selector];

  } // end getStdColor

  int scaleFooterLogo(int param)
  {
    return (param * footer_logo_scale) / 100;

  } // end scaleFooterLogo

  public LeftMenu getLeftMenu(String identifier)
  {
    return (LeftMenu)(menus.get(identifier));

  } // end getLeftMenu

  synchronized String loadStyleSheetData() throws IOException
  {
    if (stylesheet==null)
      return null;

    // Load the stylesheet data.
    StringBuffer raw_data = IOUtil.loadText(stylesheet);
    stylesheet_time = stylesheet.lastModified();
    
    // Set up the replacements map to replace the various parameters.
    HashMap vars = new HashMap();
    vars.put("font",font_face);
    vars.put("color.frame",colors_array[FRAME_BACKGROUND]);
    vars.put("color.top.background",colors_array[TITLE_BACKGROUND]);
    vars.put("color.top.foreground",colors_array[TITLE_FOREGROUND]);
    vars.put("color.top.link",colors_array[TITLE_LINK]);
    vars.put("color.left.background",colors_array[LEFT_BACKGROUND]);
    vars.put("color.left.foreground",colors_array[LEFT_FOREGROUND]);
    vars.put("color.left.link",colors_array[LEFT_LINK]);
    vars.put("color.content.background",colors_array[CONTENT_BACKGROUND]);
    vars.put("color.content.foreground",colors_array[CONTENT_FOREGROUND]);
    vars.put("color.content.header",colors_array[CONTENT_HEADER]);
    vars.put("color.disabled",colors_array[CONTENT_DISABLED]);
    vars.put("color.error",colors_array[CONTENT_ERROR]);
    vars.put("color.sidebox.top.background",colors_array[SIDEBOX_TITLE_BACKGROUND]);
    vars.put("color.sidebox.top.foreground",colors_array[SIDEBOX_TITLE_FOREGROUND]);
    vars.put("color.sidebox.background",colors_array[SIDEBOX_CONTENT_BACKGROUND]);
    vars.put("color.sidebox.foreground",colors_array[SIDEBOX_CONTENT_FOREGROUND]);
    vars.put("color.sidebox.link",colors_array[SIDEBOX_CONTENT_LINK]);
    vars.put("color.dlg.confirm.title.background",colors_array[CONFIRM_TITLE_BACKGROUND]);
    vars.put("color.dlg.confirm.title.foreground",colors_array[CONFIRM_TITLE_FOREGROUND]);
    vars.put("color.dlg.error.title.background",colors_array[ERROR_TITLE_BACKGROUND]);
    vars.put("color.dlg.error.title.foreground",colors_array[ERROR_TITLE_FOREGROUND]);
    return StringUtil.replaceAllVariables(raw_data.toString(),vars);

  } // end loadStyleSheet

  boolean hasStyleSheetChanged()
  {
    if (stylesheet==null)
      return false;
    return (stylesheet_time!=stylesheet.lastModified());

  } // end hasStyleSheetChanged

  boolean useStyleSheet()
  {
    return (stylesheet!=null);

  } // end useStyleSheet

  VeniceContent createSideBox(int id, VeniceEngine engine, UserContext uc) throws AccessError, DataException
  {
    SideBoxFactory fact = (SideBoxFactory)(sidebox_factories.get(new Integer(id)));
    if (fact==null)
      throw new DataException("invalid sidebox ID!");
    return fact.create(engine,uc);

  } // end createSideBox

  String getPhotoNotAvail()
  {
    return photo_not_avail;

  } // end getPhotoNotAvail

  boolean getPhotoNotAvailFixup()
  {
    return photo_not_avail_fixup;

  } // end getPhotoNotAvailFixup

  /*--------------------------------------------------------------------------------
   * Static operations for use by VeniceServlet
   *--------------------------------------------------------------------------------
   */

  public static RenderConfig getRenderConfig(ServletContext ctxt) throws ServletException
  {
    // Look in the servlet attributes first.
    Object obj = ctxt.getAttribute(ATTR_NAME);
    if (obj!=null)
      return (RenderConfig)obj;

    // Get the root file path.
    String root_file_path = ctxt.getRealPath("/");
    if (!(root_file_path.endsWith("/")))
      root_file_path += "/";

    // Get the parameter for the renderer's config file.
    String cfgfile = ctxt.getInitParameter(CONFIG_FILE_PARAM);
    if (!(cfgfile.startsWith("/")))
      cfgfile = root_file_path + cfgfile;
    logger.info("Initializing Venice rendering using config file: " + cfgfile);
    
    try
    { // create the RenderConfig object and save it to attributes.
      RenderConfig rconf = new RenderConfig(cfgfile,root_file_path);
      ctxt.setAttribute(ATTR_NAME,rconf);
      return rconf;

    } // end try
    catch (ConfigException e)
    { // configuration failed!  post an error message
      logger.fatal("Rendering configuration failed: " + e.getMessage(),e);
      throw new ServletException("Venice rendering configuration failed: " + e.getMessage(),e);

    } // end catch

  } // end getRenderConfig

  public static RenderData createRenderData(ServletContext ctxt, HttpServletRequest request,
					    HttpServletResponse response) throws ServletException
  {
    UserContext uc = Variables.getUserContext(ctxt,request,request.getSession(true));
    return new RenderData(getRenderConfig(ctxt),uc,ctxt,request,response);

  } // end createRenderData

  public static RenderData createRenderData(ServletContext ctxt, UserContext uc, HttpServletRequest request,
					    HttpServletResponse response) throws ServletException
  {
    return new RenderData(getRenderConfig(ctxt),uc,ctxt,request,response);

  } // end createRenderData

  /*--------------------------------------------------------------------------------
   * Static initializer
   *--------------------------------------------------------------------------------
   */

  static
  { // Initialize the mapping of color names from render-config.xml to color selector IDs.
    HashMap m = new HashMap();
    m.put("frame-bg",new Integer(FRAME_BACKGROUND));
    m.put("title-bg",new Integer(TITLE_BACKGROUND));
    m.put("title-fg",new Integer(TITLE_FOREGROUND));
    m.put("title-link",new Integer(TITLE_LINK));
    m.put("left-bg",new Integer(LEFT_BACKGROUND));
    m.put("left-fg",new Integer(LEFT_FOREGROUND));
    m.put("left-link",new Integer(LEFT_LINK));
    m.put("content-bg",new Integer(CONTENT_BACKGROUND));
    m.put("content-fg",new Integer(CONTENT_FOREGROUND));
    m.put("content-hdr",new Integer(CONTENT_HEADER));
    m.put("content-disabled",new Integer(CONTENT_DISABLED));
    m.put("content-error",new Integer(CONTENT_ERROR));
    m.put("sidebox-title-bg",new Integer(SIDEBOX_TITLE_BACKGROUND));
    m.put("sidebox-title-fg",new Integer(SIDEBOX_TITLE_FOREGROUND));
    m.put("sidebox-content-bg",new Integer(SIDEBOX_CONTENT_BACKGROUND));
    m.put("sidebox-content-fg",new Integer(SIDEBOX_CONTENT_FOREGROUND));
    m.put("sidebox-content-link",new Integer(SIDEBOX_CONTENT_LINK));
    m.put("confirm-title-bg",new Integer(CONFIRM_TITLE_BACKGROUND));
    m.put("confirm-title-fg",new Integer(CONFIRM_TITLE_FOREGROUND));
    m.put("error-title-bg",new Integer(ERROR_TITLE_BACKGROUND));
    m.put("error-title-fg",new Integer(ERROR_TITLE_FOREGROUND));
    colornames_map = Collections.unmodifiableMap(m);

  } // end static initializer

} // end class RenderConfig
