/* ************************************************************************ # # 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.schema; 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 divconq.bus.Message; import divconq.lang.op.FuncResult; import divconq.lang.op.OperationContext; import divconq.lang.op.OperationResult; import divconq.schema.DataType.DataKind; import divconq.schema.ServiceSchema.Op; import divconq.struct.CompositeStruct; import divconq.struct.ListStruct; import divconq.struct.RecordStruct; import divconq.struct.Struct; import divconq.util.ArrayUtil; import divconq.util.StringUtil; import divconq.xml.XElement; import divconq.xml.XmlReader; /** * DivConq uses a specialized type system that provides type consistency across services * (including web services), database fields and stored procedures, as well as scripting. * * All scalars (including primitives) and composites (collections) are wrapped by some * subclass of Struct. List/array collections are expressed by this class. * This class is analogous to an Array in JSON but may contain type information as well, * similar to Yaml. * * There are schema files (written in Xml and stored in the Packages repository) that define * all the known data types, including complex data types. These schema files get compiled * for for a given project and deployed as part of the conf directory. * * This class oversees the management of all the known data types as well as database * tables, stored procedures and services (including web services). */ public class SchemaManager { // composite schema of database protected DatabaseSchema db = new DatabaseSchema(this); // composite schema of database protected ServiceSchema service = new ServiceSchema(this); // types with ids protected HashMap<String, DataType> knownTypes = new HashMap<String, DataType>(); protected SchemaManager chain = null; public void setChain(SchemaManager v) { this.chain = v; } public boolean hasTable(String table) { boolean fnd = this.db.hasTable(table); if (fnd) return true; if (this.chain == null) return false; return this.chain.hasTable(table); } public List<DbTable> getDbTables() { List<DbTable> t = this.db.getTables(); if (t == null) t = new ArrayList<DbTable>(); if (this.chain == null) return t; List<DbTable> t2 = this.chain.getDbTables(); if (t2 != null) t.addAll(t2); return t; } public List<DbField> getDbFields(String table) { List<DbField> t = this.db.getFields(table); if (t == null) t = new ArrayList<DbField>(); if (this.chain == null) return t; List<DbField> t2 = this.chain.getDbFields(table); if (t2 != null) t.addAll(t2); return t; } public DbField getDbField(String table, String field) { DbField t = this.db.getField(table, field); if (t != null) return t; if (this.chain == null) return null; return this.chain.getDbField(table, field); } public DbProc getDbProc(String name) { DbProc t = this.db.getProc(name); if (t != null) return t; if (this.chain == null) return null; return this.chain.getDbProc(name); } public DbComposer getDbComposer(String name) { DbComposer t = this.db.getComposer(name); if (t != null) return t; if (this.chain == null) return null; return this.chain.getDbComposer(name); } public DbCollector getDbCollector(String name) { DbCollector t = this.db.getCollector(name); if (t != null) return t; if (this.chain == null) return null; return this.chain.getDbCollector(name); } // returns (copy) list of all triggers for all levels of the chain public List<DbTrigger> getDbTriggers(String table, String operation) { List<DbTrigger> t = this.db.getTriggers(table, operation); if (t == null) t = new ArrayList<DbTrigger>(); if (this.chain == null) return t; List<DbTrigger> t2 = this.chain.getDbTriggers(table, operation); if (t2 != null) t.addAll(t2); return t; } public OpInfo getServiceOp(String service, String feature, String op) { Op t = this.service.getOp(service, feature, op); String[] securityTags = this.service.getOpSecurity(service, feature, op); if (t != null) { OpInfo oi = new OpInfo(); oi.op = t; oi.securityTags = securityTags; return oi; } if (this.chain == null) return null; OpInfo oi = this.chain.getServiceOp(service, feature, op); if ((oi != null) && (securityTags != null)) oi.securityTags = securityTags; return oi; } public OpInfo getServiceOp(Message msg) { Op t = this.service.getOp(msg); String[] securityTags = this.service.getOpSecurity(msg); if (t != null) { OpInfo oi = new OpInfo(); oi.op = t; oi.securityTags = securityTags; return oi; } if (this.chain == null) return null; OpInfo oi = this.chain.getServiceOp(msg); if ((oi != null) && (securityTags != null)) oi.securityTags = securityTags; return oi; } public class OpInfo { protected Op op = null; protected String[] securityTags = null; public Op getOp() { return this.op; } public String[] getSecurityTags() { if ((this.securityTags != null) && (this.op != null)) return ArrayUtil.addAll(this.securityTags, this.op.securityTags); if (this.securityTags != null) return this.securityTags; if (this.op != null) return this.op.securityTags; return null; } public boolean isTagged(String... tags) { if (this.securityTags != null) { for (int i = 0; i < this.securityTags.length; i++) { String has = this.securityTags[i]; for (String wants : tags) { if (has.equals(wants)) return true; } } } if (this.op != null) return this.op.isTagged(tags); return false; } } public DataType getServiceRequest(Message msg) { DataType t = this.service.getRequestType(msg); if (t != null) return t; if (this.chain == null) return null; return this.chain.getServiceRequest(msg); } public DataType getServiceResponse(String service, String feature, String op) { DataType t = this.service.getResponseType(service, feature, op); if (t != null) return t; if (this.chain == null) return null; return this.chain.getServiceResponse(service, feature, op); } /** * @param type schema name of type * @return the schema data type */ public DataType getType(String type) { DataType t = this.knownTypes.get(type); if (t != null) return t; if (this.chain == null) return null; return this.chain.getType(type); } // ---------- public void removeService(String name) { this.service.remove(name); } /** * @return map of all known data types */ public Map<String, DataType> knownTypes() { return this.knownTypes; } /** * Take a given message and treat it is a service request - see that it is valid. * * @param msg service request * @return log of validation attempt */ public OperationResult validateRequest(Message msg){ OperationResult mr = new OperationResult(); OperationContext tc = OperationContext.get(); if (tc == null) { mr.errorTr(431); } else { OpInfo op = this.getServiceOp(msg); if (op == null) mr.errorTr(432); else if (op.op.request == null) mr.errorTr(433); else if (!msg.isVerifyRequest() && !tc.isAuthorized(op.getSecurityTags())) { mr.errorTr(434); System.out.println("cannot call: " + msg); } else op.op.request.normalizeValidate(msg); } return mr; } /** * Take a given message and treat it is a service response - see that it is valid. * * @param msg service response * @param original original request * @return log of validation attempt */ public OperationResult validateResponse(Message msg, Message original){ return this.validateResponse(msg, original.getFieldAsString("Service"), original.getFieldAsString("Feature"), original.getFieldAsString("Op")); } /** * Take a given message and treat it is a service response - see that it is valid. * * @param msg service response * @param service name * @param feature name * @param op name * @return log of validation attempt */ public OperationResult validateResponse(Message msg, String service, String feature, String op){ OperationResult mr = new OperationResult(); DataType dt = this.getServiceResponse(service, feature, op); if (dt == null) mr.errorTr(435); else dt.normalizeValidate(msg); return mr; } /** * For a given stored procedure, check that the parameters comprise a valid request. * * @param name of the procedure * @param req procedure parameters * @return log of validation attempt */ public OperationResult validateProcRequest(String name, CompositeStruct req) { OperationResult mr = new OperationResult(); DbProc proc = this.getDbProc(name); if (proc == null) mr.errorTr(426); else { // authorization is for the DB Service not here - the user is already elevated here //if (!tc.isAuthorized(proc.securityTags)) if (proc.request == null) { if ((req != null) && !req.isEmpty()) mr.errorTr(428); } else proc.request.normalizeValidate(req); } return mr; } /** * For a given stored procedure, check that the response is a valid structure. * * @param name of stored procedure * @param resp structure returned * @return log of validation attempt */ public OperationResult validateProcResponse(String name, CompositeStruct resp){ OperationResult mr = new OperationResult(); DbProc proc = this.getDbProc(name); if (proc == null) mr.errorTr(429); else { if (proc.response == null) { if ((resp != null) && !resp.isEmpty()) mr.errorTr(430); } else proc.response.normalizeValidate(resp); } return mr; } /** * For a given structure, validate that it conforms to a given schema type * * @param data structure to validate * @param type schema name of type * @return log of validation attempt */ public OperationResult validateType(Struct data, String type){ OperationResult mr = new OperationResult(); DataType dt = this.getType(type); if (dt == null) mr.errorTr(436); else dt.validate(data); return mr; } public FuncResult<Struct> normalizeValidateType(Struct data, String type){ FuncResult<Struct> mr = new FuncResult<Struct>(); DataType dt = this.getType(type); if (dt == null) { mr.errorTr(436); } else { Struct o = dt.normalizeValidate(data); if (!mr.hasErrors()) mr.setResult(o); } return mr; } /** * Create a new record structure using a schema data type. * * @param type type schema name of type * @return initialized record structure */ public RecordStruct newRecord(String type) { DataType tp = this.getType(type); if ((tp == null) || (tp.kind != DataKind.Record)) return null; return new RecordStruct(tp); } /** * Schema files contain interdependencies, after loading the files call * compile to resolve these interdependencies. */ public void compile() { // compiling not thread safe, do it once at start for (DataType dt : this.knownTypes.values()) dt.compile(); this.db.compile(); this.service.compile(); } /** * Load a file containing schema into the master schema. * * @param fl file to load * @return log of the load attempt */ public OperationResult loadSchema(Path fl) { OperationResult or = new OperationResult(); if (fl == null) { or.error(108, "Unable to apply schema file, file null"); return or; } if (Files.notExists(fl)) { or.error(109, "Missing schema file, expected: " + fl); return or; } FuncResult<XElement> xres3 = XmlReader.loadFile(fl, false); if (xres3.hasErrors()) { or.error(110, "Unable to apply schema file, missing xml"); return or; } XElement schema = xres3.getResult(); Schema s = new Schema(fl.toString(), this); s.loadSchema(schema); return or; } /** * Load type definition from an Xml element * * @param or log of the load * @param schema to associate type with * @param dtel xml source of the definition * @return the schema data type */ public DataType loadDataType(Schema schema, XElement dtel) { DataType dtype = new DataType(schema); dtype.load(dtel); if (StringUtil.isNotEmpty(dtype.id)) this.knownTypes.put(dtype.id, dtype); return dtype; } public List<DataType> lookupOptionsType(String name) { List<DataType> ld = new ArrayList<DataType>(); if (name.contains(":")) { String[] parts = name.split(":"); DataType t1 = this.getType(parts[0]); t1.compile(); if ((t1 == null) || (t1.fields == null)) { if (this.chain == null) return ld; return this.chain.lookupOptionsType(name); } Field f1 = t1.fields.get(parts[1]); if (f1 == null) { if (this.chain == null) return ld; return this.chain.lookupOptionsType(name); } return f1.options; } DataType d4 = this.getType(name); if (d4 != null) ld.add(d4); return ld; } public void loadDb(Schema schema, XElement xml) { this.db.load(schema, xml); } public void loadService(Schema schema, XElement xml) { this.service.load(schema, xml); } public ListStruct toJsonDef(String... names) { ListStruct list = new ListStruct(); for (String name : names) { DataType dt = this.getType(name); if (dt != null) list.addItem(dt.toJsonDef()); } return list; } }