/* ************************************************************************
#
# 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.hub;
import groovy.lang.GroovyObject;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import org.joda.time.DateTime;
import divconq.bus.IService;
import divconq.bus.ServiceRouter;
import divconq.filestore.bucket.Bucket;
import divconq.filestore.bucket.BucketUtil;
import divconq.io.CacheFile;
import divconq.io.FileStoreEvent;
import divconq.io.LocalFileStore;
import divconq.lang.op.FuncResult;
import divconq.lang.op.OperationContext;
import divconq.lang.op.OperationContextBuilder;
import divconq.locale.ILocaleResource;
import divconq.locale.LocaleDefinition;
import divconq.locale.Dictionary;
import divconq.scheduler.ISchedule;
import divconq.scheduler.SimpleSchedule;
import divconq.scheduler.common.CommonSchedule;
import divconq.schema.SchemaManager;
import divconq.service.DomainServiceAdapter;
import divconq.service.DomainWatcherAdapter;
import divconq.struct.ListStruct;
import divconq.struct.RecordStruct;
import divconq.util.IOUtil;
import divconq.util.ISettingsObfuscator;
import divconq.util.StringUtil;
import divconq.work.Task;
import divconq.xml.XAttribute;
import divconq.xml.XElement;
import divconq.xml.XmlReader;
public class DomainInfo implements ILocaleResource {
protected RecordStruct info = null;
protected ISettingsObfuscator obfuscator = null;
protected XElement overrideSettings = null;
protected SchemaManager schema = null;
protected Dictionary dictionary = null;
protected Map<String, IService> registered = new HashMap<String, IService>();
protected Map<String, ServiceRouter> routers = new HashMap<String, ServiceRouter>();
protected Map<String, Bucket> buckets = new HashMap<String, Bucket>();
protected DomainWatcherAdapter watcher = null;
protected List<ISchedule> schedulenodes = new ArrayList<>();
protected String locale = null;
protected LocaleDefinition localedef = null;
public String getId() {
return this.info.getFieldAsString("Id");
}
public String getAlias() {
return this.info.getFieldAsString("Alias");
}
public String getTitle() {
return this.info.getFieldAsString("Title");
}
public RecordStruct getInfo() {
return this.info;
}
public IService getService(String name) {
return this.registered.get(name);
}
public ServiceRouter getServiceRouter(String name) {
return this.routers.get(name);
}
public Bucket getBucket(String name) {
Bucket b = this.buckets.get(name);
if (b == null) {
b = BucketUtil.buildBucket(name, this);
if (b != null)
this.buckets.put(name, b);
}
return b;
}
public GroovyObject getScript(String service, String feature) {
IService s = this.registered.get(service);
if (s instanceof DomainServiceAdapter)
return ((DomainServiceAdapter)s).getScript(feature);
return null;
}
public GroovyObject getWatcherScript() {
if (this.watcher != null)
return this.watcher.getScript();
return null;
}
public ISettingsObfuscator getObfuscator() {
return this.obfuscator;
}
public ListStruct getNames() {
return this.info.getFieldAsList("Names");
}
public XElement getSettings() {
if (this.overrideSettings != null)
return this.overrideSettings;
return this.info.getFieldAsXml("Settings");
}
public SchemaManager getSchema() {
if (this.schema != null)
return this.schema;
return Hub.instance.getSchema();
}
@Override
public String getDefaultLocale() {
if (this.locale != null)
return this.locale;
return this.getParentLocaleResource().getDefaultLocale();
}
@Override
public LocaleDefinition getDefaultLocaleDefinition() {
return this.getLocaleDefinition(this.getDefaultLocale());
}
@Override
public Dictionary getDictionary() {
if (this.dictionary != null)
return this.dictionary;
return this.getParentLocaleResource().getDictionary();
}
@Override
public LocaleDefinition getLocaleDefinition(String name) {
// TODO lookup definitions
return new LocaleDefinition(name);
}
// 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;
}
@Override
public ILocaleResource getParentLocaleResource() {
return Hub.instance.getResources();
}
public void load(RecordStruct info) {
this.info = info;
this.obfuscator = DomainInfo.prepDomainObfuscator(
info.getFieldAsString("ObscureClass"),
info.getFieldAsString("ObscureSeed"));
this.reloadSettings();
}
public Path resolvePath(String path) {
LocalFileStore fs = Hub.instance.getPublicFileStore();
if (fs == null)
return null;
if (StringUtil.isEmpty(path))
return fs.getFilePath().resolve("dcw/" + this.getAlias());
if (path.charAt(0) == '/')
return fs.getFilePath().resolve("dcw/" + this.getAlias() + path);
return fs.getFilePath().resolve("dcw/" + this.getAlias() + "/" + path);
}
public Path getPath() {
LocalFileStore fs = Hub.instance.getPublicFileStore();
if (fs == null)
return null;
return fs.getFilePath().resolve("dcw/" + this.getAlias());
}
public String relativize(Path path) {
path = path.normalize().toAbsolutePath();
if (path == null)
return null;
String rpath = path.toString().replace('\\', '/');
String dpath = this.getPath().toString().replace('\\', '/');
if (!rpath.startsWith(dpath))
return null;
return rpath.substring(dpath.length());
}
public CacheFile resolveCachePath(String path) {
LocalFileStore fs = Hub.instance.getPublicFileStore();
if (fs == null)
return null;
if (StringUtil.isEmpty(path))
return fs.cacheResolvePath("dcw/" + this.getAlias());
if (path.charAt(0) == '/')
return fs.cacheResolvePath("dcw/" + this.getAlias() + path);
return fs.cacheResolvePath("dcw/" + this.getAlias() + "/" + path);
}
/* TODO reload more settings too - consider:
*
./dcw/[domain alias]/config holds web setting for domain
- settings.xml are the general settings (dcmHomePage - dcmDefaultTemplate[path]) - direct edit by web dev
(includes code tags that map to classes instead of to groovy)
- dictionary.xml is the domain level dictionary - direct edit by web dev
- vars.json is the domain level variable store - direct edit by web dev
- cms-settings.xml is extra settings - editable in CMS
- cms-dictionary.xml is the domain level dictionary - editable in CMS
- cms-vars.json is the domain level variable store - editable in CMS
*
*/
public void reloadSettings() {
this.overrideSettings = null;
Path cpath = this.resolvePath("/config");
if ((cpath == null) || Files.notExists(cpath))
return;
Path cspath = cpath.resolve("settings.xml");
if (Files.exists(cspath)) {
FuncResult<CharSequence> res = IOUtil.readEntireFile(cspath);
if (res.isEmptyResult())
return;
FuncResult<XElement> xres = XmlReader.parse(res.getResult(), true);
if (xres.isEmptyResult())
return;
this.overrideSettings = xres.getResult();
if (this.overrideSettings.hasAttribute("Locale"))
this.locale = this.overrideSettings.getAttribute("Locale");
}
// TODO check for and load dictionaries, variables, etc
this.schema = null;
Path shpath = cpath.resolve("schema.xml");
if (Files.exists(shpath)) {
this.schema = new SchemaManager();
this.schema.setChain(Hub.instance.getSchema());
this.schema.loadSchema(shpath);
this.schema.compile();
}
// dictionary
this.dictionary = null;
Path dicpath = cpath.resolve("dictionary.xml");
if (Files.exists(dicpath)) {
this.dictionary = new Dictionary();
this.dictionary.setParent(Hub.instance.getResources().getDictionary());
this.dictionary.load(dicpath);
this.localedef = this.getLocaleDefinition(this.getDefaultLocale());
}
this.registered.clear();
this.routers.clear();
Path dpath = this.resolvePath("");
Path spath = dpath.resolve("services");
if (Files.exists(spath)) {
try (Stream<Path> str = Files.list(spath)) {
str.forEach(path -> {
// only directories are services - files in dir are features
if (!Files.isDirectory(path))
return;
String name = path.getFileName().toString();
this.registerService(new DomainServiceAdapter(name, path, dpath));
});
}
catch (IOException x) {
// TODO Auto-generated catch block
x.printStackTrace();
}
}
// watcher comes after services so it can register a service if it likes... if this came before it would be cleared from the registered list
if (this.watcher == null)
this.watcher = new DomainWatcherAdapter(dpath);
this.watcher.init(this);
for (Bucket b : this.buckets.values())
b.tryExecuteMethod("Kill", this);
this.buckets.clear();
this.prepDomainSchedule();
}
public void registerService(IService service) {
this.registered.put(service.serviceName(), service);
ServiceRouter r = new ServiceRouter(service.serviceName());
r.indexLocal();
this.routers.put(service.serviceName(), r);
}
@Override
public String toString() {
return this.getTitle();
}
static public ISettingsObfuscator prepDomainObfuscator(String obclass, String seed) {
ISettingsObfuscator obfuscator = null;
if (StringUtil.isEmpty(obclass))
obclass = "divconq.util.BasicSettingsObfuscator";
try {
obfuscator = (ISettingsObfuscator) Hub.instance.getInstance(obclass);
}
catch (Exception x) {
OperationContext.get().error("Bad Settings Obfuscator");
return null;
}
XElement clock1 = Hub.instance.getConfig().find("Clock");
String obid = (clock1 != null) ? clock1.getAttribute("Id") : null;
obfuscator.init(new XElement("Clock",
new XAttribute("Id", obid),
new XAttribute("Feed", seed)
));
return obfuscator;
}
public void prepDomainSchedule() {
// cancel and remove any previous schedules
if (this.schedulenodes.size() > 0) {
OperationContext.get().info("Cancelling schedules for " + this.getAlias());
for (ISchedule sch : this.schedulenodes) {
OperationContext.get().info("- schedule: " + sch.task().getTitle());
sch.cancel();
}
}
this.schedulenodes.clear();
if (this.overrideSettings == null)
return;
// now load new schedules
OperationContext.get().info("Prepping schedules for " + this.getAlias());
for (XElement schedule : this.overrideSettings.selectAll("Schedules/*")) {
OperationContext.get().info("- find schedule: " + schedule.getAttribute("Title"));
ISchedule sched = "CommonSchedule".equals(schedule.getName()) ? new CommonSchedule() : new SimpleSchedule();
sched.init(schedule);
sched.setTask(new Task()
.withId(Task.nextTaskId("DomainSchedule"))
.withTitle("Domain Scheduled Task: " + schedule.getAttribute("Title"))
.withContext(new OperationContextBuilder().withRootTaskTemplate().withDomainId(this.getId()).toOperationContext())
.withWork(trun -> {
OperationContext.get().info("Executing schedule: " + trun.getTask().getTitle() + " for domain " + trun.getTask().getContext().getDomain().getAlias());
if (schedule.hasAttribute("MethodName") && (this.watcher != null))
this.watcher.tryExecuteMethod(schedule.getAttribute("MethodName"), new Object[] { trun });
})
);
OperationContext.get().info("- prepped schedule: " + schedule.getAttribute("Title") + " next run " + new DateTime(sched.when()));
this.schedulenodes.add(sched);
Hub.instance.getScheduler().addNode(sched);
}
}
public void fileChanged(FileStoreEvent result) {
this.watcher.tryExecuteMethod("FileChanged", new Object[] { result });
}
public void fireAfterReindex() {
this.watcher.tryExecuteMethod("AfterReindex", new Object[] { });
}
}