/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.web;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import divconq.filestore.CommonPath;
import divconq.hub.DomainInfo;
import divconq.hub.Hub;
import divconq.hub.HubEvents;
import divconq.hub.IEventSubscriber;
import divconq.io.FileStoreEvent;
import divconq.io.LocalFileStore;
import divconq.lang.op.FuncCallback;
import divconq.mod.ExtensionLoader;
import divconq.mod.IModule;
import divconq.net.IpAddress;
import divconq.util.MimeUtil;
import divconq.util.StringUtil;
import divconq.web.http.SslContextFactory;
import divconq.xml.XElement;
public class WebSiteManager {
protected IModule module = null;
protected IWebExtension webExtension = null;
protected ConcurrentHashMap<String, WebDomain> dsitemap = new ConcurrentHashMap<String, WebDomain>();
//protected String version = null;
protected String defaultTlsPort = "443";
protected List<SslContextFactory> tls = new ArrayList<>();
// TODO when module is unloaded, clean up all references to classes
protected Map<String,IWebMacro> macros = new HashMap<String,IWebMacro>();
protected ValuesMacro vmacros = new ValuesMacro();
protected List<XElement> devices = new ArrayList<XElement>();
public String getDefaultTlsPort() {
return this.defaultTlsPort;
}
/*
public String getVersion() {
return this.version;
}
*/
public IModule getModule() {
return this.module;
}
public void start(IModule module, XElement config) {
this.module = module;
if (config != null) {
for (XElement scel : config.selectAll("SslContext")) {
SslContextFactory tls = new SslContextFactory();
tls.init(config, scel);
this.tls.add(tls);
}
this.defaultTlsPort = config.getAttribute("DefaultTlsPort", this.defaultTlsPort);
XElement settings = config.find("ViewSettings");
if (settings != null) {
// ideally we would only load in Hub level settings, try to use sparingly
MimeUtil.load(settings);
this.devices = settings.selectAll("DeviceRule");
for (XElement macros : settings.selectAll("Macro")) {
String name = macros.getAttribute("Name");
if (StringUtil.isEmpty(name))
continue;
String bname = macros.getAttribute("Class");
if (StringUtil.isNotEmpty(bname)) {
Class<?> cls = Hub.instance.getClass(bname);
if (cls != null) {
Class<? extends IWebMacro> tcls = cls.asSubclass(IWebMacro.class);
if (tcls != null)
try {
this.macros.put(name, tcls.newInstance());
}
catch (Exception x) {
x.printStackTrace();
}
}
// TODO log
//System.out.println("unable to load class: " + cname);
}
String value = macros.getAttribute("Value");
if (StringUtil.isNotEmpty(value))
this.vmacros.add(name, value);
}
}
}
for (ExtensionLoader el : module.getLoader().getExtensions()) {
if (el.getExtension() instanceof IWebExtension) {
this.webExtension = (IWebExtension) el.getExtension();
break;
}
}
// prepare extensions (web apps)
//XElement lcf = module.getLoader().getConfig();
/* TODO try to recreate this concept
Adler32 ad = new Adler32();
*/
/*
if (lcf != null)
for(XElement node : lcf.selectAll("Extension")) {
String name = node.getAttribute("Name");
Bundle bundle = this.module.getLoader().getExtension(name);
if (! (bundle instanceof ExtensionLoader))
continue;
IExtension ex = ((ExtensionLoader)bundle).getExtension();
if (! (ex instanceof IWebExtension))
continue;
// only compute if this is a web extension
/* TODO try to recreate this concept
bundle.adler(ad);
* /
IWebExtension sm = (IWebExtension)ex;
this.extensions.put(name, sm);
if (this.defaultExtension == null)
this.defaultExtension = name;
}
*/
/* TODO try to recreate this concept
this.version = Long.toHexString(ad.getValue());
*/
// ========================================================================
/**
* - ./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
*/
FuncCallback<FileStoreEvent> localfilestorecallback = new FuncCallback<FileStoreEvent>() {
@Override
public void callback() {
this.resetCalledFlag();
CommonPath p = this.getResult().getPath();
//System.out.println(p);
// only notify on www updates
if (p.getNameCount() < 4)
return;
// must be inside a domain or we don't care
String mod = p.getName(0);
String domain = p.getName(1);
String section = p.getName(2);
if (!"dcw".equals(mod) || (!"www".equals(section) && !"www-preview".equals(section) && !"feed".equals(section) && !"feed-preview".equals(section)))
return;
for (WebDomain wdomain : WebSiteManager.this.dsitemap.values()) {
if (domain.equals(wdomain.getAlias())) {
wdomain.dynNotify();
// TODO after we merge DomainInfo and WebDomain features this will work better,
// right now feed only gets imported if the domain has been loaded via HTTP(S) request
/*
if ("feed".equals(section) || "feed-preview".equals(section)) {
Task task = new Task()
.withWork(new IWork() {
@Override
public void run(TaskRun trun) {
ImportWebsiteTool iutil = new ImportWebsiteTool();
// TODO use domain path resolution
iutil.importFeedFile(Paths.get("./public" + p), new OperationCallback() {
@Override
public void callback() {
trun.complete();
}
});
}
})
.withTitle("Importing feed " + p)
.withBucket("ServicePool") // only one at a time
.withContext(new OperationContextBuilder()
.withRootTaskTemplate()
.withDomainId(wdomain.getId())
.toOperationContext()
);
Hub.instance.getWorkPool().submit(task);
}
*/
break;
}
}
}
};
// register for file store events
LocalFileStore pubfs = Hub.instance.getPublicFileStore();
if (pubfs != null)
pubfs.register(localfilestorecallback);
LocalFileStore privfs = Hub.instance.getPrivateFileStore();
if (privfs != null)
privfs.register(localfilestorecallback);
/**
* - ./packages/zCustomPublic/www/dcf/index.html
* - ./packages/dc/dcFilePublic/www/dcf/index.html
* - ./packages/dcWeb/www/dcf/index.html
*/
FuncCallback<FileStoreEvent> localpackagecallback = new FuncCallback<FileStoreEvent>() {
@Override
public void callback() {
for (WebDomain domain : WebSiteManager.this.dsitemap.values())
domain.dynNotify();
this.resetCalledFlag();
}
};
// register for file store events
LocalFileStore packfs = Hub.instance.getResources().getPackages().getPackageFileStore();
if (packfs != null)
packfs.register(localpackagecallback);
/* TODO
Hub.instance.listenOnline(new OperationCallback() {
@Override
public void callback() {
// load dynamic web parts only when domains are loaded by Hub
for (IWebExtension ext : WebSiteManager.this.extensions.values())
ext.loadDynamic();
}
} );
*/
Hub.instance.subscribeToEvent(HubEvents.DomainConfigChanged, new IEventSubscriber() {
@Override
public void eventFired(Object e) {
DomainInfo di = (DomainInfo) e;
for (WebDomain domain : WebSiteManager.this.dsitemap.values()) {
if (di.getId().equals(domain.getId()))
domain.settingsNotify();
}
}
});
}
public WebDomain getDomain(String id) {
DomainInfo di = Hub.instance.getDomainInfo(id);
if (di != null) {
WebDomain domain = this.dsitemap.get(di.getId());
if (domain != null)
return domain;
for (DomainInfo d : Hub.instance.getDomains().getDomains()) {
if (d.getId().equals(id)) {
domain = new WebDomain();
domain.init(d, this);
this.dsitemap.put(id, domain);
return domain;
}
}
}
return null;
}
public void online() {
this.dsitemap.clear();
}
static public String resolveHost(Request req) {
return WebSiteManager.resolveHost(req.getHeader("Host"));
}
static public String resolveHost(String dname) {
if (StringUtil.isNotEmpty(dname)) {
int cpos = dname.indexOf(':');
if (cpos > -1)
dname = dname.substring(0, cpos);
}
if (StringUtil.isEmpty(dname) || IpAddress.isIpLiteral(dname))
dname = "localhost";
return dname;
}
public DomainInfo resolveDomainInfo(Request req) {
return Hub.instance.getDomains().resolveDomainInfo(WebSiteManager.resolveHost(req));
}
public DomainInfo resolveDomainInfo(String dname) {
return Hub.instance.getDomains().resolveDomainInfo(WebSiteManager.resolveHost(dname));
}
// only call this with normalized hostnames
public SslContextFactory findSslContextFactory(String hostname) {
DomainInfo di = Hub.instance.getDomains().resolveDomainInfo(hostname);
if (di != null) {
WebDomain wd = this.getDomain(di.getId());
if (wd != null) {
SslContextFactory scf = wd.getSecureContextFactory(hostname);
if (scf != null)
return scf;
}
}
for (SslContextFactory cf : this.tls)
if (cf.keynameMatch(hostname))
return cf;
if (this.tls.size() > 0)
return this.tls.get(0);
return null;
}
public IWebMacro getMacro(String name) {
if (this.vmacros.hasKey(name))
return this.vmacros;
return this.macros.get(name);
}
public IWebExtension getWebExtension() {
return this.webExtension;
}
/*
public IWebExtension getExtension(String name) {
return this.extensions.get(name);
}
public String getDefaultName() {
return this.defaultExtension;
}
*/
public List<String> getDevice(Request req) {
List<String> res = new ArrayList<String>();
String agent = req.getHeader("User-Agent");
if (StringUtil.isEmpty(agent)) {
res.add("simple");
res.add("std");
res.add("mobile");
return res;
}
for (XElement el : this.devices) {
boolean fnd = (el.findIndex("AnyAgent") > -1);
if (!fnd) {
for (XElement fel : el.selectAll("IfAgent")) {
if (agent.contains(fel.getAttribute("Contains"))) {
fnd = true;
break;
}
}
}
if (fnd) {
for (XElement fel : el.selectAll("TryDevice")) {
String name = fel.getAttribute("Name");
if (StringUtil.isNotEmpty(name))
res.add(name);
}
break;
}
}
return res;
}
}