/* ************************************************************************
#
# 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 java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import divconq.lang.op.FuncResult;
import divconq.lang.op.OperationContext;
import divconq.lang.op.OperationResult;
import divconq.locale.Dictionary;
import divconq.locale.ILocaleResource;
import divconq.locale.LocaleDefinition;
import divconq.log.DebugLevel;
import divconq.schema.SchemaManager;
import divconq.struct.Struct;
import divconq.util.StringUtil;
import divconq.xml.XElement;
import divconq.xml.XmlReader;
/**
* Within dcFramework all features are tied together through the Hub class. To get
* the hub going you need to give it access to some resources such as Schema
* and config.
*
* HubResources is the class that ties all the resources together and enables
* Hub to start. A typical application using dcFramework will start up something
* like this:
*
* HubResources resources = new HubResources("00101", true);
* resources.setDebugLevel(DebugLevel.Warn);
* OperationResult or = resources.init();
*
* if (or.hasErrors()) {
* Logger.error("Unable to continue, hub resources not properly initialized");
* return;
* }
*
* Hub.instance.start(resources);
*
* [TODO add link to Quick Start ]
*
* [TODO add link to Framework Architecture ]
*
*
* TODO consider an option where all config is loaded from AWS. Their description:
*
* You can use this data to build more generic AMIs that can be modified by configuration files supplied at launch
* time. For example, if you run web servers for various small businesses, they can all use the same AMI and retrieve
* their content from the Amazon S3 bucket you specify at launch. To add a new customer at any time, simply create a
* bucket for the customer, add their content, and launch your AMI.
*
*
* @author Andy
*
*/
public class HubResources implements ILocaleResource {
static public boolean isValidHubId(String id) {
if (StringUtil.isEmpty(id) || (id.length() != 5))
return false;
for (int i = 0; i < 5; i++)
if (!Character.isDigit(id.charAt(i)))
return false;
if ("00000".equals(id))
return false;
return true;
}
protected String deployment = "dcFileServer";
protected String hubid = "00001"; // reserved for utilities and stand alones - 00000 reserved for system/core
protected String team = "one";
protected String squad = "one";
protected HubMode mode = HubMode.Private; // Gateway, Public (server), Private (server or utility)
protected boolean forTesting = false;
protected DebugLevel startuplevel = DebugLevel.Info;
// post init
protected boolean initialized = false;
protected boolean initsuccess = false;
protected HubPackages packages = new HubPackages();
protected XElement config = null;
// TODO
//protected XElement fabric = null;
protected Dictionary dictionary = null;
protected String locale = "en";
protected LocaleDefinition localedef = null;
protected SchemaManager schemaman = null;
/**
* HubId is a 5 digit (zero padded) number that uniquely identifies this Hub (process) in
* the distributed network of Hubs (processes) in your Project (application).
*
* @return HubId
*/
public String getHubId() {
return this.hubid;
}
/**
* HubId is a 5 digit (zero padded) number that uniquely identifies this Hub (process) in
* the distributed network of Hubs (processes) in your Project (application).
*
* You should only set the HubId once per run, Hubs are not designed to change Ids mid run.
*
* @param v HubId
*/
public void setHubId(String v) {
if (!HubResources.isValidHubId(v))
throw new IllegalArgumentException("Hub id must be 5 digits, zero padded. Id 00000 is reserved.");
OperationContext.setHubId(v);
this.hubid = v;
}
/**
* false: this Hub is running on a private, fire-walled, network (company LAN)
* true: this Hub is running on a public network such as the Internet or in a DMZ
*
* @return true if is public facing
*/
public boolean isPublicFacing() {
return (this.mode == HubMode.Gateway) || (this.mode == HubMode.Public);
}
/**
* true: this Hub is not complete software, but is mostly support for network interchange
*
* @return true if is a gateway
*/
public boolean isGateway() {
return (this.mode == HubMode.Gateway);
}
public boolean isForTesting() {
return this.forTesting;
}
/**
* Squad Id is used to group a number of Hubs together. A Squad is functionally separated from
* all other Squads and may operate independently (disaster recovery). A Squad may be active
* even while other Squads are, this is not a fail-over architecture but a active-active architecture.
* Squads may often be placed in separate data centers or spread across data centers and clouds (IaaS).
* A Squad has its own copy of the Project database, the database is replicated so that each Squad
* will eventually be consistent.
*
* @return Squad Id
*/
public String getSquadId() {
return this.squad;
}
/**
* Squad Id is used to group a number of Hubs together. A Squad is functionally separated from
* all other Squads and may operate independently (disaster recovery). A Squad may be active
* even while other Squads are, this is not a fail-over architecture but a active-active architecture.
* Squads may often be placed in separate data centers or spread across data centers and clouds (IaaS).
* A Squad has its own copy of the Project database, the database is replicated so that each Squad
* will eventually be consistent.
*
* @param v Squad Id
*/
public void setSquadId(String v) {
this.squad = v;
}
public String getTeamId() {
return this.team;
}
public void setTeamId(String v) {
this.team = v;
}
public HubMode getMode() {
return this.mode;
}
/**
* The global (default) Debug Level to use normally comes from the config file, before the config
* file is available Hub start-up uses the Debug Level given to this class.
*
* @return Debug Level used during start-up
*/
public DebugLevel getDebugLevel() {
return this.startuplevel;
}
/**
* The global (default) Debug Level to use normally comes from the config file, before the config
* file is available Hub start-up uses the Debug Level given to this class.
*
* @param v Debug Level used during start-up
*/
public void setDebugLevel(DebugLevel v) {
this.startuplevel = v;
}
/**
* A list of package names in the order in which the packages are loaded. Each subsequent package overrides
* the previous. So, for example, the contents of the last package overrides any similar resource in all
* the previous packages.
*
* @return list of package names
*/
public HubPackages getPackages() {
return this.packages;
}
/**
* Schema holds the custom data types used by this Project.
*
* @return custom data type definitions
*/
public SchemaManager getSchema() {
return this.schemaman;
}
/**
* Dictionary holds a list of Locales for which translations exists for this Project.
*
* @return Locales for this Project
*/
@Override
public Dictionary getDictionary() {
return this.dictionary;
}
/**
* Config is an XML structure holding the master settings for this Project. Other configuration
* may come from the database.
*
* @return master Project settings
*/
public XElement getConfig() {
return this.config;
}
/* TODO support chronology defaults
//.withChronology("/" + DateTimeZone.getDefault().getID()); // ISOChronology w/ default zone
*
*/
/* TODO
public XElement getFabric() {
return this.fabric;
}
*/
public HubResources() {
}
/**
* Manage the resources for this Hub by indicating which hub this is and whether to run
* in developer mode. See class, HubId and DevMode comments for details.
*
* @param deployment project name for the servers (one or more squads)
* @param squad the group of servers that forms a local operating group (one or more teams)
* @param team the team of servers within the squad (one or more hubs)
* @param hubid Hub Id
*/
public HubResources(String deployment, String squad, String team, String hubid) {
if (StringUtil.isNotEmpty(deployment))
this.deployment = deployment;
if (StringUtil.isNotEmpty(squad))
this.squad = squad;
if (StringUtil.isNotEmpty(team))
this.team = team;
if (StringUtil.isNotEmpty(hubid))
this.setHubId(hubid);
}
/**
* Initialize this object by loading the Schema, Dictionary, Config and Fabric.
*
* When in Dev Mode the resources are loaded from a local copy of the Repository instead of from
* the config directory. Developers will debug applications using the local Repository copy
* so that they can edit repository artifacts (schema, dictionary, resource files) in "native"
* repository structure.
*
* @return messages logged while initializing this object
*/
public OperationResult init() {
// do not run init twice (not thread safe, should be called by main thread only)
if (this.initialized) {
OperationResult or = new OperationResult();
if (!this.initsuccess)
or.error(112, "Hub resources already loaded, but contained errors");
return or;
}
this.initialized = true;
// before starting we want to have a valid hub level task context
// which requires a hub id
OperationContext.setHubId(this.hubid);
// use the startup debug level until we init Logger settings
OperationResult or = new OperationResult();
or.info(0, "Loading hub resources");
or.trace(0, "Loading shared config");
File fshared = new File("./config/" + this.deployment + "/_shared.xml");
FuncResult<XElement> xres = XmlReader.loadFile(fshared, false);
if (xres.hasErrors()) {
or.error(100, "Unable to load _shared.xml file, expected: " + fshared.getAbsolutePath());
return or;
}
XElement cel = xres.getResult();
this.packages.load(cel);
or.trace(0, "Packages loaded: " + this.packages);
or.trace(0, "Loading config.xml file");
// find the right config file
File f = new File("./config/" + this.deployment + "/" + this.hubid + ".xml");
if (!f.exists())
f = new File("./config/" + this.deployment + "/" + this.team + ".xml");
if (!f.exists())
f = new File("./config/" + this.deployment + "/" + this.squad + ".xml");
if (!f.exists())
f = new File("./config/" + this.deployment + "/_config.xml");
if (!f.exists()) {
or.error(101, "Unable to find config.xml file, expected: " + f.getAbsolutePath());
return or;
}
FuncResult<XElement> xres2 = XmlReader.loadFile(f, false);
if (xres2.hasErrors()) {
or.error(102, "Unable to load config file, expected: " + f.getAbsolutePath());
return or;
}
this.config = xres2.getResult();
if (this.config.hasAttribute("HubId"))
this.setHubId(this.config.getAttribute("HubId"));
if (this.config.hasAttribute("Team"))
this.setTeamId(this.config.getAttribute("Team"));
if (this.config.hasAttribute("Squad"))
this.setSquadId(this.config.getAttribute("Squad"));
if (this.config.hasAttribute("Mode"))
this.mode = HubMode.valueOf(this.config.getAttribute("Mode"));
if (this.config.hasAttribute("ForTesting"))
this.forTesting = Struct.objectToBooleanOrFalse(this.config.getAttribute("ForTesting"));
if (this.config.hasAttribute("Locale"))
this.locale = this.config.getAttribute("Locale", "en");
or.trace(0, "Loaded config.xml file at: " + f.getAbsolutePath());
or.trace(0, "Using project compiler to load schema and dictionary");
ProjectCompiler comp = new ProjectCompiler();
this.schemaman = comp.getSchema(this.packages);
if (or.hasErrors()) {
or.exit(103, "Unable to load schema file(s)");
return or;
}
or.trace(0, "Schema loaded");
this.dictionary = comp.getDictionary(or, this.packages);
if (or.hasErrors()) {
or.exit(104, "Unable to load dictionary file(s)");
return or;
}
// ready to add definitions
this.localedef = this.getLocaleDefinition(this.getDefaultLocale());
or.trace(0, "Dictionary loaded");
// TODO get fabric from ./project...
// TODO load Clock Xml from http://169.254.169.254/latest/user-data
// then over write
//this.config.find("Clock").replace(parsed awssource);
or.info(0, "Hub resources loaded");
this.initsuccess = true;
return or;
}
@Override
public String getDefaultLocale() {
return this.locale;
}
@Override
public LocaleDefinition getDefaultLocaleDefinition() {
return this.getLocaleDefinition(this.getDefaultLocale());
}
@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;
return -1;
}
@Override
public ILocaleResource getParentLocaleResource() {
return null;
}
/**
* Scan through the local repository or config directory and reload the dictionary
* files. After this any translation used will have updates from the dictionary files.
* This method is really only useful in development mode and is not typically called
* by application code.
*
* @return messages logged while reloading dictionary
*/
public OperationResult reloadDictionary() {
OperationResult or = new OperationResult();
or.trace(0, "Loading Dictionary");
ProjectCompiler comp = new ProjectCompiler();
this.dictionary = comp.getDictionary(or, this.packages);
if (or.hasErrors())
or.exit(104, "Unable to load dictionary file(s)");
else
or.trace("Dictionary loaded");
return or;
}
/**
* Get a reference to a resource file specific for this Project.
*
* @param filename name of the file, path relative to the resources/ folder in config
* @return Path reference if found, if not error messages in FuncResult
*/
public FuncResult<Path> getProjectResource(String filename) {
FuncResult<Path> res = new FuncResult<>();
Path f = Paths.get("./config/" + this.deployment + "/resources/" + filename);
if (Files.exists(f))
res.setResult(f);
else
res.errorTr(201, f.toString());
return res;
}
}