package divconq.web; import io.netty.handler.codec.http.cookie.Cookie; import io.netty.handler.codec.http.cookie.DefaultCookie; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import divconq.filestore.CommonPath; import divconq.hub.Hub; import divconq.io.CacheFile; import divconq.io.LocalFileStore; import divconq.lang.op.OperationContext; import divconq.lang.op.OperationContextBuilder; import divconq.lang.op.UserContext; import divconq.locale.Dictionary; import divconq.locale.ILocaleResource; import divconq.locale.LocaleDefinition; import divconq.log.Logger; import divconq.util.MimeUtil; import divconq.util.StringUtil; import divconq.web.ui.adapter.GasOutputAdapter; import divconq.web.ui.adapter.SsiOutputAdapter; import divconq.web.ui.adapter.StaticOutputAdapter; import divconq.web.ui.adapter.DcuiOutputAdapter; import divconq.xml.XElement; public class WebSite implements ILocaleResource { protected String alias = null; protected WebDomain domain = null; // these are only meaningful for sub sites protected SiteIntegration integration = SiteIntegration.Files; protected String locale = null; protected LocaleDefinition localedef = null; protected Dictionary dictionary = null; protected Map<String, LocaleDefinition> locales = new HashMap<>(); protected Map<String, LocaleDefinition> domainlocales = new HashMap<>(); protected Map<String, DcuiOutputAdapter> dyncache = new HashMap<>(); protected Map<String, DcuiOutputAdapter> dynpreviewcache = new HashMap<>(); public WebDomain getDomain() { return this.domain; } public String getAlias() { return this.alias; } public SiteIntegration getIntegration() { return this.integration; } @Override public Dictionary getDictionary() { if (this.dictionary != null) return this.dictionary; return this.getParentLocaleResource().getDictionary(); } @Override public ILocaleResource getParentLocaleResource() { return this.domain.getDomainInfo(); } @Override public String getDefaultLocale() { if (this.locale != null) return this.locale; return this.getParentLocaleResource().getDefaultLocale(); } @Override public LocaleDefinition getLocaleDefinition(String name) { // TODO lookup definitions return new LocaleDefinition(name); } @Override public LocaleDefinition getDefaultLocaleDefinition() { if (this.localedef != null) return this.localedef; return this.getParentLocaleResource().getDefaultLocaleDefinition(); } // 0 is best, higher the number the worse, -1 for not supported @Override public int rateLocale(String locale) { if ((this.localedef != null) && this.localedef.match(locale)) return 0; int r = this.getParentLocaleResource().rateLocale(locale); if (r < 0) return -1; return r + 1; } public WebSite(String alias, WebDomain domain) { this.alias = alias; this.domain = domain; } public void init(XElement config) { this.dictionary = null; this.locales.clear(); // for a sub-site, may have additional dict to load if (this != this.domain.getRootSite()) { LocalFileStore pubfs = Hub.instance.getPublicFileStore(); Path cpath = pubfs.resolvePath("/dcw/" + this.domain.getAlias() + "/sites/" + this.alias + "/config"); if ((cpath != null) && Files.exists(cpath)) { // dictionary Path dicpath = cpath.resolve("dictionary.xml"); if (Files.exists(dicpath)) { this.dictionary = new Dictionary(); this.dictionary.setParent(this.domain.getDomainInfo().getDictionary()); this.dictionary.load(dicpath); } } } if (config != null) { // this settings are only valid for sub sites if (this != this.domain.getRootSite()) { if (config.hasAttribute("Integration")) { try { this.integration = SiteIntegration.valueOf(config.getAttribute("Integration", "Files")); } catch (Exception x) { this.integration = SiteIntegration.Files; } } } if (config.hasAttribute("Locale")) { this.locale = config.getAttribute("Locale"); this.localedef = this.getLocaleDefinition(this.locale); // add the list of locales supported for this site this.locales.put(this.locale, this.localedef); } // these settings are valid for root and sub sites for (XElement pel : config.selectAll("Locale")) { String lname = pel.getAttribute("Name"); if (StringUtil.isEmpty(lname)) continue; LocaleDefinition def = this.getLocaleDefinition(lname); this.locales.put(lname, def); for (XElement del : pel.selectAll("Domain")) { String dname = del.getAttribute("Name"); if (StringUtil.isEmpty(lname)) continue; this.domainlocales.put(dname, def); } } } } public Cookie resolveLocale(HttpContext context, UserContext usr, OperationContextBuilder ctxb) { if (this.locales.size() == 0) { // make sure we have at least 1 locale listed for the site String lvalue = this.getDefaultLocale(); // add the list of locales supported for this site this.locales.put(lvalue, this.getLocaleDefinition(lvalue)); } LocaleDefinition locale = null; // see if the path indicates a language CommonPath path = context.getRequest().getPath(); if (path.getNameCount() > 0) { String lvalue = path.getName(0); locale = this.locales.get(lvalue); // extract the language from the path if (locale != null) context.getRequest().setPath(path.subpath(1)); } // but respect the cookie if it matches something though Cookie langcookie = context.getRequest().getCookie("dcLang"); if (locale == null) { if (langcookie != null) { String lvalue = langcookie.value(); // if everything checks out set the op locale and done if (this.locales.containsKey(lvalue)) { ctxb.withOperatingLocale(lvalue); return null; } locale = this.getLocaleDefinition(lvalue); // use language if variant - still ok and done if (locale.hasVariant()) { if (this.locales.containsKey(locale.getLanguage())) { ctxb.withOperatingLocale(lvalue); // keep the variant part, it may be used in places on site - supporting a lang implicitly allows all variants return null; } } // otherwise ignore the cookie, will replace it } } // see if the domain is set for a specific language if (locale == null) { String domain = context.getRequest().getHeader("Host"); if (domain.indexOf(':') > -1) domain = domain.substring(0, domain.indexOf(':')); locale = this.domainlocales.get(domain); } // see if the user has a preference if (locale == null) { String lvalue = usr.getLocale(); if (StringUtil.isNotEmpty(lvalue)) locale = this.locales.get(lvalue); } // if we find any locale at all then to see if it is the default // if not use it, else use the default if ((locale != null) && !locale.equals(this.getDefaultLocaleDefinition())) { ctxb.withOperatingLocale(locale.getName()); return new DefaultCookie("dcLang", locale.getName()); } // clear the cookie if we are to use default locale if (langcookie != null) return new DefaultCookie("dcLang", ""); // we are using default locale, nothing more to do return null; } public boolean isSharedSection(String section) { if (this.integration == SiteIntegration.None) return false; if (this.integration == SiteIntegration.Full) return true; if ("files".equals(section) || "galleries".equals(section)) return true; return false; } // something changed in the www folder // force compiled content to reload from file system public void dynNotify() { this.dyncache.clear(); this.dynpreviewcache.clear(); } /* public IOutputAdapter findFile(CommonPath path, boolean isPreview) { return this.domain.findFile(this, path, isPreview); } */ /** * File paths come in as /dcf/index.html but really they are in - * * Domain Path Map: * "/dcf/index.html" * * Domain Private Phantom Files: (draft/preview mode files) * ./private/dcw/[domain id]/www-preview/dcf/index.html * * Domain Private Override Files: * ./private/dcw/[domain id]/www/dcf/index.html * * Domain Phantom Files: (draft/preview mode files) * ./public/dcw/[domain id]/www-preview/dcf/index.html * * Domain Override Files: * ./public/dcw/[domain id]/www/dcf/index.html * * Package Files: * ./packages/[package id]/www/dcf/index.html * * Example: * - ./private/dcw/filetransferconsulting/www-preview/dcf/index.html * - ./private/dcw/filetransferconsulting/www/dcf/index.html * - ./public/dcw/filetransferconsulting/www-preview/dcf/index.html * - ./public/dcw/filetransferconsulting/www/dcf/index.html * - ./packages/zCustomPublic/www/dcf/index.html * - ./packages/dc/dcFilePublic/www/dcf/index.html * - ./packages/dcWeb/www/dcf/index.html * * * @param ctx * @return an adapter that can execute to generate web response */ public IOutputAdapter findFile(WebContext ctx) { return this.findFile(ctx.getRequest().getPath(), ctx.isPreview()); } public IOutputAdapter findFile(CommonPath path, boolean isPreview) { // ===================================================== // if request has an extension do specific file lookup // ===================================================== if (Logger.isDebug()) Logger.debug("find file before ext check: " + path + " - " + isPreview); // if we have an extension then we don't have to do the search below // never go up a level past a file (or folder) with an extension if (path.hasFileExtension()) { CacheFile wpath = this.findFilePath(path, isPreview); if (wpath != null) return this.pathToAdapter(isPreview, path, wpath); // TODO not found file!! OperationContext.get().errorTr(150007); return null; } // ===================================================== // if request does not have an extension look for files // that might match this path or one of its parents // using the special extensions // ===================================================== if (Logger.isDebug()) Logger.debug("find file before dyn check: " + path + " - " + isPreview); // we get here if we have no extension - thus we need to look for path match with specials int pdepth = path.getNameCount(); // check path maps first - hopefully the request has been mapped at one of the levels while (pdepth > 0) { CommonPath ppath = path.subpath(0, pdepth); if (isPreview) { IOutputAdapter ioa = this.dynpreviewcache.get(ppath.toString()); if (ioa != null) return ioa; } IOutputAdapter ioa = this.dyncache.get(ppath.toString()); if (ioa != null) return ioa; pdepth--; } if (Logger.isDebug()) Logger.debug("find file not cached: " + path + " - " + isPreview); // if not in dyncache then look on file system CacheFile wpath = this.findFilePath(path, isPreview); if (wpath == null) { OperationContext.get().errorTr(150007); return null; } if (Logger.isDebug()) Logger.debug("find file path: " + wpath + " - " + path + " - " + isPreview); return this.pathToAdapter(isPreview, path, wpath); } public IOutputAdapter pathToAdapter(boolean isPreview, CommonPath path, CacheFile cfile) { IOutputAdapter ioa = null; String filename = cfile.getPath(); HtmlMode hmode = this.domain.getHtmlMode(); if (filename.endsWith(".dcui.xml") || filename.endsWith(".dcuis.xml")) { DcuiOutputAdapter voa = new DcuiOutputAdapter(); (isPreview ? this.dynpreviewcache : this.dyncache).put(path.toString(), voa); ioa = voa; } else if (filename.endsWith(".shtml") || ((hmode == HtmlMode.Ssi) && filename.endsWith(".html"))) { ioa = new SsiOutputAdapter(); } else if (filename.endsWith(".gas")) { ioa = new GasOutputAdapter(); } // certain resource types cannot be delivered else if (filename.endsWith(".class")) { OperationContext.get().errorTr(150001); return null; } else { ioa = new StaticOutputAdapter(); } ioa.init(this, cfile, path, isPreview); return ioa; } public CacheFile findFilePath(CommonPath path, boolean isPreview) { // figure out which section we are looking in String sect = "www"; if ("files".equals(path.getName(0)) || "galleries".equals(path.getName(0))) { sect = path.getName(0); path = path.subpath(1); } if (Logger.isDebug()) Logger.debug("find file path: " + path + " in " + sect); // ===================================================== // if request has an extension do specific file lookup // ===================================================== // if we have an extension then we don't have to do the search below // never go up a level past a file (or folder) with an extension if (path.hasFileExtension()) return this.findSectionFile(sect, path.toString(), isPreview); // ===================================================== // if request does not have an extension look for files // that might match this path or one of its parents // using the special extensions // ===================================================== if (Logger.isDebug()) Logger.debug("find file path dyn: " + path + " in " + sect); // we get here if we have no extension - thus we need to look for path match with specials int pdepth = path.getNameCount(); // check file system while (pdepth > 0) { CommonPath ppath = path.subpath(0, pdepth); for (String ext : this.domain.getSpecialExtensions()) { CacheFile cfile = this.findSectionFile(sect, ppath.toString() + ext, isPreview); if (cfile != null) return cfile; } pdepth--; } OperationContext.get().errorTr(150007); return null; } /* public CacheFile findSectionFile(WebContext ctx, String section, String path) { return this.findSectionFile(ctx.getSite(), section, path, ctx.isPreview()); } */ public CacheFile findSectionFile(String section, String path, boolean isPreview) { if (Logger.isDebug()) Logger.debug("find section file: " + path + " in " + section); LocalFileStore pubfs = Hub.instance.getPublicFileStore(); LocalFileStore prifs = Hub.instance.getPrivateFileStore(); // for a sub-site, check first in the site folder if (this != this.domain.getRootSite()) { if (Logger.isDebug()) Logger.debug("find section file, check site: " + path + " in " + section); if (prifs != null) { if (isPreview) { CacheFile cfile = prifs.cacheResolvePath("/dcw/" + this.domain.getAlias() + "/sites/" + this.alias + "/" + section + "-preview" + path); if (cfile != null) return cfile; } CacheFile cfile = prifs.cacheResolvePath("/dcw/" + this.domain.getAlias() + "/sites/" + this.alias + "/" + section + path); if (cfile != null) return cfile; } if (pubfs != null) { if (isPreview) { CacheFile cfile = pubfs.cacheResolvePath("/dcw/" + this.domain.getAlias() + "/sites/" + this.alias + "/" + section + "-preview" + path); if (cfile != null) return cfile; } CacheFile cfile = pubfs.cacheResolvePath("/dcw/" + this.domain.getAlias() + "/sites/" + this.alias + "/" + section + path); if (cfile != null) return cfile; } // if not shared then jump right to modules for resource if (!this.isSharedSection(section)) return Hub.instance.getResources().getPackages().cacheLookupPath(this.domain.getPackagelist(), "/" + section + path); } // now check the root site folders // TODO Each site's special files (dcui, dcf, html, shtml, gas) are completely separate - supplemental files like js, css, imgs, etc may be integrated depending on site settings. if (Logger.isDebug()) Logger.debug("find section file, check root: " + path + " in " + section); if (prifs != null) { if (isPreview) { CacheFile cfile = prifs.cacheResolvePath("/dcw/" + this.domain.getAlias() + "/" + section + "-preview" + path); if (cfile != null) return cfile; } CacheFile cfile = prifs.cacheResolvePath("/dcw/" + this.domain.getAlias() + "/" + section + path); if (cfile != null) return cfile; } if (pubfs != null) { if (isPreview) { CacheFile cfile = pubfs.cacheResolvePath("/dcw/" + this.domain.getAlias() + "/" + section + "-preview" + path); if (cfile != null) return cfile; } CacheFile cfile = pubfs.cacheResolvePath("/dcw/" + this.domain.getAlias() + "/" + section + path); if (cfile != null) return cfile; } if (Logger.isDebug()) Logger.debug("find section check packages: " + path + " in " + section); return Hub.instance.getResources().getPackages().cacheLookupPath(this.domain.getPackagelist(), "/" + section + path); } public String getMimeType(String ext) { // TODO check settings Site before system - no move to domain, domain is where settings / data go return MimeUtil.getMimeType(ext); } public boolean getMimeCompress(String mime) { // TODO check settings Site before system - no move to domain, domain is where settings / data go return MimeUtil.getMimeCompress(mime); } }