venice-main-classic/src/com/silverwrist/venice/ui/config/RootConfig.java
2002-01-16 17:39:09 +00:00

798 lines
26 KiB
Java

/*
* 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-02 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved.
*
* Contributor(s):
*/
package com.silverwrist.venice.ui.config;
import java.io.*;
import java.util.*;
import org.apache.log4j.*;
import org.w3c.dom.*;
import com.silverwrist.util.*;
import com.silverwrist.venice.core.CommunityContext;
import com.silverwrist.venice.except.*;
import com.silverwrist.venice.ui.*;
import com.silverwrist.venice.ui.dlg.Dialog;
import com.silverwrist.venice.ui.menus.CommunityMenu;
import com.silverwrist.venice.ui.menus.CommunityMenuFactory;
import com.silverwrist.venice.ui.menus.Menu;
import com.silverwrist.venice.ui.menus.MenuComponent;
import com.silverwrist.venice.ui.rpc.XmlRpcMethod;
import com.silverwrist.venice.util.*;
public class RootConfig implements LinkTypes, ColorSelectors
{
/*--------------------------------------------------------------------------------
* Static data members
*--------------------------------------------------------------------------------
*/
private static final String VENICE_URL = "http://venice.sourceforge.net";
private static final String VENICE_IMAGE = "powered-by-venice.gif";
private static final int VENICE_IMAGE_WIDTH = 129;
private static final int VENICE_IMAGE_HEIGHT = 103;
private static final String VENICE_ALT = "Powered By Venice";
private static Category logger = Category.getInstance(RootConfig.class);
private static final Map link_types;
/*--------------------------------------------------------------------------------
* Attributes
*--------------------------------------------------------------------------------
*/
private String script_directory; // the scripts directory
private String rpc_script_directory; // the RPC scripts directory
private String temp_directory; // the temporary directory
private String image_path; // the images path
private String static_path; // the static files path
private String external_static_path; // the external static files path
private String format_path; // the JSP formatter path
private String blank_photo_path; // the "photo not available" path
private String frame_jsp_name; // the name of the frame JSP
private String site_title; // the title for the site
private File stylesheet; // the stylesheet file reference
private long stylesheet_lastchange; // when was the stylesheet last changed?
private boolean smart_tags; // do we want to allow M$ IE6 Smart Tags?
private String site_logo_img_tag; // the <IMG> tag containing the site logo
private String site_logo_href = null; // the HREF to surround the site logo with
private int site_logo_href_type = -1; // the type of the above href
private String venice_logo_tag; // the HTML snippet containing the Venice logo
private String page_icon_tags = null; // the HTML snippet containing the page icons
private String font_face; // the default font face name
private String base_font; // the default <BASEFONT> tag
private Map font_sizes; // the stock font sizes
private ColorPalette colors; // all the colors
private boolean html_comments; // do we want to embed HTML comments?
private ButtonHolder buttons; // the button definitions
private String[] content_hdr; // the content header parts
private Remapper remapper; // the URL remapper
private List xmlrpc_methods; // the list of XML-RPC methods
private StockMessages stock_messages; // the stock messages
private Map menus; // the menus
private DialogManager dialogs; // the dialog manager
private SideBoxManager sideboxes; // the sidebox manager
private CommunityMenuFactory comm_menu_fact; // the community menu factory
/*--------------------------------------------------------------------------------
* Constructor
*--------------------------------------------------------------------------------
*/
public RootConfig(String config_file, String root_file_path) throws ConfigException
{
// Load the configuration file.
XMLLoader loader = XMLLoader.get();
Document doc = loader.loadConfigDocument(config_file);
Element root = loader.configGetRootElement(doc,"ui-config");
DOMElementHelper root_h = new DOMElementHelper(root);
// Get the <file-paths/> section.
Element sect = loader.configGetSubSection(root_h,"file-paths");
DOMElementHelper sect_h = new DOMElementHelper(sect);
// Get the full pathname of the sidebox configuration file.
String sidebox_config = loader.configGetSubElementText(sect_h,"sidebox-config");
if (!(sidebox_config.startsWith("/")))
sidebox_config = root_file_path + sidebox_config;
// Get the full pathname of the services configuration file.
String services_config = loader.configGetSubElementText(sect_h,"services-config");
if (!(services_config.startsWith("/")))
services_config = root_file_path + services_config;
// Get the full pathname of the script directory.
script_directory = loader.configGetSubElementText(sect_h,"script-dir");
if (!(script_directory.startsWith("/")))
script_directory = root_file_path + script_directory;
if (!(script_directory.endsWith("/")))
script_directory = script_directory + "/";
// Test to make sure the script directory exists.
File t_file = new File(script_directory);
if (!(t_file.isDirectory()))
{ // script directory does not exist - throw exception
logger.fatal("<script-dir/> directory \"" + script_directory + "\" is not a directory");
throw new ConfigException("specified <script-dir/> is not a directory",
sect_h.getSubElement("script-dir"));
} // end if
// Get the full pathname of the RPC script directory.
rpc_script_directory = loader.configGetSubElementText(sect_h,"rpc-script-dir");
if (!(rpc_script_directory.startsWith("/")))
rpc_script_directory = root_file_path + rpc_script_directory;
if (!(rpc_script_directory.endsWith("/")))
rpc_script_directory = rpc_script_directory + "/";
// Test to make sure the RPC script directory exists.
t_file = new File(rpc_script_directory);
if (!(t_file.isDirectory()))
{ // script directory does not exist - throw exception
logger.fatal("<rpc-script-dir/> directory \"" + rpc_script_directory + "\" is not a directory");
throw new ConfigException("specified <rpc-script-dir/> is not a directory",
sect_h.getSubElement("rpc-script-dir"));
} // end if
// Get the full pathname of the temporary directory.
temp_directory = loader.configGetSubElementText(sect_h,"temp-dir");
if (!(temp_directory.startsWith("/")))
temp_directory = root_file_path + temp_directory;
// Test to make sure the temporary directory exists.
t_file = new File(temp_directory);
if (!(t_file.isDirectory()))
{ // temporary directory does not exist - throw exception
logger.fatal("<temp-dir/> directory \"" + temp_directory + "\" is not a directory");
throw new ConfigException("specified <temp-dir/> is not a directory",
sect_h.getSubElement("temp-dir"));
} // end if
// Get the <uri-paths/> section.
sect = loader.configGetSubSection(root_h,"uri-paths");
sect_h = new DOMElementHelper(sect);
// Get the images path.
image_path = loader.configGetSubElementText(sect_h,"image");
if (!(image_path.endsWith("/")))
image_path = image_path + "/";
// Get the static path.
static_path = loader.configGetSubElementText(sect_h,"static");
if (!(static_path.endsWith("/")))
static_path = static_path + "/";
// Get the external static path.
external_static_path = loader.configGetSubElementText(sect_h,"external-static");
if (!(external_static_path.endsWith("/")))
external_static_path = external_static_path + "/";
// Get the format JSP path.
format_path = loader.configGetSubElementText(sect_h,"format-jsp");
if (!(format_path.endsWith("/")))
format_path = format_path + "/";
// Get the pathname of the "photo not available" image.
blank_photo_path = loader.configGetSubElementText(sect_h,"photo-not-avail");
Element sect1 = sect_h.getSubElement("photo-not-avail");
DOMElementHelper sect1_h = new DOMElementHelper(sect1);
if (sect1_h.hasAttribute("fixup"))
blank_photo_path = image_path + blank_photo_path;
// Get the <frame/> section.
sect = loader.configGetSubSection(root_h,"frame");
sect_h = new DOMElementHelper(sect);
// Get the name of the frame JSP.
String tmp = loader.configGetSubElementText(sect_h,"jsp-name");
frame_jsp_name = format_path + tmp;
// Get the site title.
site_title = sect_h.getSubElementText("site-title");
// Get the base font size.
Integer bf_size = sect_h.getSubElementInt("basefont-size");
if (bf_size==null)
bf_size = new Integer(3);
// Get the default stylesheet location.
tmp = sect_h.getSubElementText("stylesheet");
if ((tmp!=null) && !(tmp.startsWith("/")))
tmp = root_file_path + tmp;
if (tmp!=null)
{ // set up stylesheet file name and stylesheet change data
stylesheet = new File(tmp);
if (stylesheet.canRead())
stylesheet_lastchange = stylesheet.lastModified();
else
{ // the stylesheet file does not exist
logger.fatal("specified <stylesheet/> file \"" + tmp + "\" does not exist");
throw new ConfigException("specified <stylesheet/> file does not exist",
sect_h.getSubElement("stylesheet"));
} // end else
} // end if
else
{ // just null these values out
stylesheet = null;
stylesheet_lastchange = 0;
} // end else
// Get the "Smart Tags" flag.
smart_tags = sect_h.hasChildElement("ms-copyright-violations");
// Retrieve the site logo.
sect1 = loader.configGetSubSection(sect_h,"site-logo");
sect1_h = new DOMElementHelper(sect1);
StringBuffer tmpbuf = new StringBuffer("<IMG SRC=\"");
tmpbuf.append(loader.configGetText(sect1_h)).append("\" ALT=\"").append(site_title).append("\" WIDTH=");
// Get the logo width.
Integer itmp = sect1_h.getAttributeInt("width");
if (itmp==null)
itmp = new Integer(140);
tmpbuf.append(itmp).append(" HEIGHT=");
// Get the logo height.
itmp = sect1_h.getAttributeInt("height");
if (itmp==null)
itmp = new Integer(80);
tmpbuf.append(itmp).append(" HSPACE=");
// Get the horizontal spacing for the logo.
itmp = sect1_h.getAttributeInt("hspace");
if (itmp==null)
itmp = new Integer(2);
tmpbuf.append(itmp).append(" VSPACE=");
// Get the vertical spacing for the logo.
itmp = sect1_h.getAttributeInt("vspace");
if (itmp==null)
itmp = new Integer(2);
tmpbuf.append(itmp).append(" BORDER=0>");
site_logo_img_tag = tmpbuf.toString();
// Get the link URL of the logo.
tmp = sect1.getAttribute("href");
if (!(StringUtil.isStringEmpty(tmp)))
{ // save off the HREF, get the type of the link
site_logo_href = tmp;
tmp = loader.configGetAttribute(sect1,"type");
itmp = (Integer)(link_types.get(tmp));
if (itmp==null)
{ // this is not good!
logger.fatal("<site-logo/> type=\"" + tmp + "\" is not a valid value");
throw new ConfigException("invalid link type for <site-logo/>",sect1);
} // end if
site_logo_href_type = itmp.intValue();
} // end if
// Get the footer logo scale and build the HTML snippet that contains the Venice logo.
itmp = sect_h.getSubElementInt("footer-logo-scale");
if (itmp==null)
itmp = new Integer(100);
int tmp_width = (VENICE_IMAGE_WIDTH * itmp.intValue()) / 100;
int tmp_height = (VENICE_IMAGE_HEIGHT * itmp.intValue()) / 100;
venice_logo_tag = "<A HREF=\"" + VENICE_URL + "\" TARGET=\"_blank\"><IMG SRC=\"" + image_path
+ VENICE_IMAGE + "\" ALT=\"" + VENICE_ALT + "\" WIDTH=" + tmp_width + " HEIGHT="
+ tmp_height + " BORDER=0 HSPACE=0 VSPACE=0></A>";
// Get the page icon and icon type, and the "favorites icon" (MS-specific).
String page_icon_1 = null, page_icon_2 = null;
sect1 = sect_h.getSubElement("favicon");
if (sect1!=null)
{ // get the URL and create the "shortcut icon" tag
String url = loader.configGetText(sect1);
page_icon_2 = "<LINK REL=\"SHORTCUT ICON\" HREF=\"" + url + "\">\n";
} // end if
sect1 = sect_h.getSubElement("page-icon");
if (sect1!=null)
{ // get the URL and type, and create the page icon tag
String url = loader.configGetText(sect1);
String type = loader.configGetAttribute(sect1,"type");
page_icon_1 = "<LINK REL=\"icon\" HREF=\"" + url + "\" TYPE=\"" + type + "\">\n";
if (page_icon_2==null) // fill this in for the "shortcut icon" as well
page_icon_2 = "<LINK REL=\"SHORTCUT ICON\" HREF=\"" + url + "\">\n";
} // end if
if (page_icon_1==null)
page_icon_tags = page_icon_2; // just use "shortcut icon"
else
page_icon_tags = page_icon_1 + page_icon_2; // use both tags
// Get the <rendering/> section.
sect = loader.configGetSubSection(root_h,"rendering");
sect_h = new DOMElementHelper(sect);
// Get the default font face name.
font_face = loader.configGetSubElementText(sect_h,"font");
base_font = "<BASEFONT FACE=\"" + font_face + "\" SIZE=" + bf_size.intValue() + ">";
// Load the stock font sizes.
sect1 = sect_h.getSubElement("font-sizes");
HashMap tmap;
NodeList nl;
int i;
if (sect1!=null)
{ // scan through this subsection to find the stock font sizes
tmap = new HashMap();
nl = sect1.getChildNodes();
for (i=0; i<nl.getLength(); i++)
{ // loop through the subelements
Node n = nl.item(i);
if (n.getNodeType()==Node.ELEMENT_NODE)
{ // retrieve the font size and add it to our map
DOMElementHelper h = new DOMElementHelper((Element)n);
tmap.put(n.getNodeName(),h.getElementText());
} // end if
} // end for
// save off the returned sizes
if (tmap.isEmpty())
font_sizes = Collections.EMPTY_MAP;
else
font_sizes = Collections.unmodifiableMap(tmap);
} // end if
else // no font sizes - just leave this empty
font_sizes = Collections.EMPTY_MAP;
// Load all the colors.
colors = new ColorPalette(loader.configGetSubSection(sect_h,"colors"));
// Set up the content header array.
content_hdr = new String[5];
content_hdr[0] = "<SPAN CLASS=\"chead1\">" + getFontTag(CONTENT_HEADER,"header") + "<B>";
content_hdr[1] = "</B></FONT></SPAN>";
content_hdr[2] = "&nbsp;&nbsp;<SPAN CLASS=\"chead2\">" + getFontTag(CONTENT_HEADER,"subhead") + "<B>";
content_hdr[3] = "</B></FONT></SPAN>";
content_hdr[4] = "<HR ALIGN=LEFT SIZE=2 WIDTH=\"90%\" NOSHADE>\n";
// Get the "HTML Comments" flag.
html_comments = sect_h.hasChildElement("html-comments");
// Get the "Buttons" section and initialize it.
sect1 = loader.configGetSubSection(sect_h,"buttons");
buttons = new ButtonHolder(sect1,image_path);
// Get the <remapper/> section.
sect = loader.configGetSubSection(root_h,"remapper");
// Initialize the remapper object.
remapper = new Remapper(sect,this);
// Get the <rpc/> section.
sect = loader.configGetSubSection(root_h,"rpc");
sect_h = new DOMElementHelper(sect);
// Get the <xmlrpc-methods/> section.
sect1 = loader.configGetSubSection(sect_h,"xmlrpc-methods");
nl = sect1.getChildNodes();
ArrayList tmp_alist = new ArrayList();
for (i=0; i<nl.getLength(); i++)
{ // look for methods
Node n = nl.item(i);
if (n.getNodeType()==Node.ELEMENT_NODE)
{ // look for a <method/> element and use it to initialize a method
if (n.getNodeName().equals("method"))
tmp_alist.add(new XmlRpcMethod(this,(Element)n));
} // end if
} // end for
if (tmp_alist.isEmpty())
xmlrpc_methods = Collections.EMPTY_LIST;
else
{ // save off the methods list
tmp_alist.trimToSize();
xmlrpc_methods = Collections.unmodifiableList(tmp_alist);
} // end else
// Get the <messages/> section.
sect = loader.configGetSubSection(root_h,"messages");
// Initialize the stock messages list.
stock_messages = new StockMessages(sect);
// Get the <menu-definitions/> section.
sect = loader.configGetSubSection(root_h,"menu-definitions");
tmap = new HashMap();
nl = sect.getChildNodes();
for (i=0; i<nl.getLength(); i++)
{ // look for <menudef> subnodes and use them to initialize menus
Node n = nl.item(i);
if (n.getNodeType()==Node.ELEMENT_NODE)
{ // verify that it's a menu definition, then get its ID and build a menu
loader.configVerifyNodeName(n,"menudef");
String menuid = loader.configGetAttribute((Element)n,"id");
Menu m = new Menu((Element)n,this,menuid);
tmap.put(menuid,m);
} // end if
// else just ignore it
} // end for
if (tmap.isEmpty())
menus = Collections.EMPTY_MAP;
else
menus = Collections.unmodifiableMap(tmap);
// Get the <dialog-definitions/> section.
sect = loader.configGetSubSection(root_h,"dialog-definitions");
// Initialize the dialog manager.
dialogs = new DialogManager(this,sect);
// done with the ui-config.xml file
// Load up the sidebox-config.xml file.
doc = loader.loadConfigDocument(sidebox_config);
root = loader.configGetRootElement(doc,"sidebox-config");
// Create the sidebox manager.
sideboxes = new SideBoxManager(root);
// done with the sidebox-config.xml file
// Load up the services-config.xml file.
doc = loader.loadConfigDocument(services_config);
root = loader.configGetRootElement(doc,"services-config");
root_h = new DOMElementHelper(root);
// Get the community section and pass it to the CommunityMenuFactory.
comm_menu_fact = new CommunityMenuFactory(root_h.getSubElement("community"),this);
} // end constructor
/*--------------------------------------------------------------------------------
* Internal operations
*--------------------------------------------------------------------------------
*/
private final String mapFontSize(String sz)
{
String rc = (String)(font_sizes.get(sz));
return ((rc==null) ? sz : rc);
} // end mapFontSize
/*--------------------------------------------------------------------------------
* External operations
*--------------------------------------------------------------------------------
*/
public final String getTemporaryPath()
{
return temp_directory;
} // end getTemporaryPath
public final String getImagePath(String img)
{
return image_path + img;
} // end getImagePath
public final String getStaticPath(String s)
{
return static_path + s;
} // end getImagePath
public final String getExternalStaticPath(String s)
{
return external_static_path + s;
} // end getExternalStaticPath
public final String getFormatJSPPath(String jsp)
{
return format_path + jsp;
} // end getFormatJSPPath
public final String getScriptPath(String sname)
{
return script_directory + sname;
} // end getScriptPath
public final String getRPCScriptPath(String sname)
{
return rpc_script_directory + sname;
} // end getRPCScriptPath
public final String getFrameJSPName()
{
return frame_jsp_name;
} // end getFrameJSPName
public final String getPageTitle(String t)
{
if (site_title==null)
return t;
else
return t + " - " + site_title;
} // end getPageTitle
public final String getBaseFontTag()
{
return base_font;
} // end getBaseFontTag
public final boolean usingStyleSheet()
{
return (stylesheet!=null);
} // end usingStyleSheet
public final boolean hasStyleSheetChanged()
{
if (stylesheet==null)
return false;
return (stylesheet.lastModified()!=stylesheet_lastchange);
} // end hasStyleSheetChanged
public final String loadStyleSheetData() throws IOException
{
StringBuffer raw_data;
synchronized (this)
{ // If there's no stylesheet, don't bother.
if (stylesheet==null)
return null;
// Load the stylesheet data.
raw_data = IOUtil.loadText(stylesheet);
stylesheet_lastchange = stylesheet.lastModified();
} // end synchronized block
// Set up the replacements map to replace the various parameters.
HashMap vars = new HashMap();
vars.put("font",font_face);
colors.fillParameterMap("color.",vars);
return StringUtil.replaceAllVariables(raw_data.toString(),vars);
} // end loadStyleSheetData
public final String getColor(int selector)
{
return colors.getColor(selector);
} // end getColor
public final String getColor(String name)
{
return colors.getColor(name);
} // end getColor
public final String getFontTag(int colorsel, int size)
{
return "<FONT FACE=\"" + font_face + "\" COLOR=\"" + colors.getColor(colorsel) + "\" SIZE=" + size + ">";
} // end getFontTag
public final String getFontTag(String color, int size)
{
StringBuffer rc = new StringBuffer("<FONT FACE=\"");
rc.append(font_face).append("\" SIZE=").append(size);
if (color!=null)
rc.append(" COLOR=\"").append(colors.getColor(color)).append("\"");
rc.append('>');
return rc.toString();
} // end getFontTag
public final String getFontTag(int colorsel, String size)
{
StringBuffer rc = new StringBuffer("<FONT FACE=\"");
rc.append(font_face).append("\" COLOR=\"").append(colors.getColor(colorsel)).append('\"');
if (size!=null)
rc.append(" SIZE=\"").append(mapFontSize(size)).append('\"');
rc.append('>');
return rc.toString();
} // end getFontTag
public final String getFontTag(String color, String size)
{
StringBuffer rc = new StringBuffer("<FONT FACE=\"");
rc.append(font_face).append("\"");
if (color!=null)
rc.append(" COLOR=\"").append(colors.getColor(color)).append("\"");
if (size!=null)
rc.append(" SIZE=\"").append(mapFontSize(size)).append('\"');
rc.append('>');
return rc.toString();
} // end getFontTag
public final boolean useSmartTags()
{
return smart_tags;
} // end useSmartTags
public final boolean useHTMLComments()
{
return html_comments;
} // end useHTMLComments
public final String getSiteLogoImageTag()
{
return site_logo_img_tag;
} // end getSiteLogoImageTag
public final String getSiteLogoLink()
{
return site_logo_href;
} // end getSiteLogoLink
public final int getSiteLogoLinkType()
{
return site_logo_href_type;
} // end getSiteLogoLinkType
public final int convertLinkType(String str)
{
Integer tmp = (Integer)(link_types.get(str.trim().toLowerCase()));
return (tmp==null) ? -1 : tmp.intValue();
} // end convertLinkType
public final String getStockMessage(String key)
{
return stock_messages.get(key);
} // end getStockMessage
public final String getStockMessage(String key, Map vars)
{
return stock_messages.getReplace(key,vars);
} // end getStockMessage
public final Remapper getRemapper()
{
return remapper;
} // end getRemapper
public final String getVeniceLogoTag()
{
return venice_logo_tag;
} // end getVeniceLogoTag
public final String getPageIconTags()
{
return page_icon_tags;
} // end getPageIconTags
public final String getContentHeader(String primary, String secondary)
{
StringBuffer buf = new StringBuffer(content_hdr[0]);
buf.append(StringUtil.encodeHTML(primary)).append(content_hdr[1]);
if (secondary!=null)
buf.append(content_hdr[2]).append(StringUtil.encodeHTML(secondary)).append(content_hdr[3]);
buf.append(content_hdr[4]);
return buf.toString();
} // end getContentHeader
public final MenuComponent getMenu(String name)
{
return (MenuComponent)(menus.get(name));
} // end getMenu
public final MenuComponent getMenu(String name, Map vars)
{
return new Menu((Menu)(menus.get(name)),vars);
} // end getMenu
public final String getButtonVisual(String id)
{
return buttons.getButtonVisual(id);
} // end getButtonVisual
public final String getButtonInput(String id)
{
return buttons.getButtonInput(id);
} // end getButtonInput
public final Dialog getDialog(String name)
{
return dialogs.getDialog(name);
} // end getDialog
public final SideBoxManager getSideBoxManager()
{
return sideboxes;
} // end getSideBoxManager
public final String getBlankPhoto()
{
return blank_photo_path;
} // end getBlankPhoto
public final CommunityMenu getCommunityMenu(CommunityContext comm)
{
return comm_menu_fact.createMenu(comm);
} // end getCommunityMenu
public final String getDefaultServletAddress(RequestInput inp, CommunityContext comm)
{
return comm_menu_fact.getDefaultServletAddress(inp,comm);
} // end getDefaultServletAddress
public final List getXmlRpcMethods()
{
return xmlrpc_methods;
} // end getXmlRpcMethods
/*--------------------------------------------------------------------------------
* Static initializer
*--------------------------------------------------------------------------------
*/
static
{
HashMap m = new HashMap();
m.put("absolute",new Integer(ABSOLUTE));
m.put("servlet",new Integer(SERVLET));
m.put("frame",new Integer(FRAME));
link_types = Collections.unmodifiableMap(m);
} // end static initializer
} // end class RootConfig