diff --git a/.classpath b/.classpath index 4f45250..505ae12 100644 --- a/.classpath +++ b/.classpath @@ -13,6 +13,12 @@ + + + + + + diff --git a/.gitignore b/.gitignore index 3ba0238..de245b0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ .vscode/ !.vscode/launch.json - +build/ +dist/ +target/ +var/ ### Java ### *.class @@ -21,9 +24,6 @@ hs_err_pid* ### Gradle ### .gradle -build/ -dist/ -target/ # Ignore Gradle GUI config gradle-app.setting diff --git a/build.gradle b/build.gradle index f537589..312dd4f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,23 @@ +/** +Local repostories: +Unix - ~/.m2 +Windows - C:\Users\\.m2 +For example - /Users/alex/.m2/repository///. + */ + apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'application' -mainClassName = 'com.reliancy.jabba.Router' +apply plugin: 'maven-publish' + +group='com.reliancy' +version = '0.1' +mainClassName = group+'.'+name+'.Router' +System.out.println("group:"+group); +System.out.println("name:"+name); +System.out.println("version:"+version); +System.out.println("entry:"+mainClassName); + repositories { mavenLocal() mavenCentral() @@ -12,8 +28,8 @@ targetCompatibility = 1.8 dependencies { implementation "org.eclipse.jetty:jetty-server:11.0.1" implementation "org.slf4j:slf4j-simple:2.0.0-alpha0" - implementation 'com.hubspot.jinjava:jinjava:2.5.10' - implementation 'com.hubspot.jinjava:jinjava:2.5.10' + //implementation 'com.hubspot.jinjava:jinjava:2.5.10' + implementation 'com.github.jknack:handlebars:4.2.1' implementation 'com.h2database:h2:1.4.200' // https://mvnrepository.com/artifact/org.postgresql/postgresql implementation 'org.postgresql:postgresql:42.3.1' @@ -43,20 +59,21 @@ test { } } jar { - archiveBaseName = 'jabba' - archiveVersion = '0.1' + archiveBaseName = project.name + archiveVersion = project.version manifest { attributes "Main-Class": mainClassName attributes "Class-Path": configurations.runtimeClasspath.collect { it.getName() }.join(' ') } } task copyToLib(type: Copy) { - into "${buildDir}/libs" from configurations.runtimeClasspath + //into "${buildDir}/libs" from configurations.runtimeClasspath + into layout.buildDirectory.dir("libs") from configurations.runtimeClasspath } -build.dependsOn(copyToLib) +build.finalizedBy(copyToLib) task fat_jar(type: Jar) { - archiveBaseName = 'fat-jabba' - archiveVersion = '0.1' + archiveBaseName = 'fat-'+project.name + archiveVersion = project.version duplicatesStrategy = DuplicatesStrategy.EXCLUDE /* manifest { @@ -86,7 +103,7 @@ class Server implements Runnable{ } return singleton; } - org.slf4j.Logger log=org.slf4j.LoggerFactory.getLogger("server.driver"); + //org.slf4j.Logger log=org.slf4j.LoggerFactory.getLogger("server.driver"); Thread driver=null; Runnable task=null; protected Server(){} @@ -107,10 +124,8 @@ class Server implements Runnable{ info("running task"); try{ task.run(); - }catch(java.lang.InterruptedException ex){ + }catch(java.lang.Exception ex){ info("running task:interrupted"); - }catch(org.gradle.internal.UncheckedException ex2){ - info("running task:interrupted2"); } } public Server start(Runnable c){ @@ -165,16 +180,21 @@ task runServer{ //args "arg1", "arg2" */ } -// build.gradle -eclipse.classpath { - defaultOutputDir = file("build") ///default - file.whenMerged { cp -> - cp.entries.forEach { cpe -> - if (cpe instanceof org.gradle.plugins.ide.eclipse.model.SourceFolder) { - cpe.output = cpe.output.replace "bin/", "build/classes/java/" - } - if (cpe instanceof org.gradle.plugins.ide.eclipse.model.Output) { - cpe.path = cpe.path.replace "bin/", "build/" +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + } + } +} +eclipse{ + classpath { + defaultOutputDir = file("build") ///default + file.whenMerged { cp -> + cp.entries.forEach { cpe -> + if (cpe.kind == 'src' && cpe.hasProperty('output')) { + cpe.output = cpe.output.replace('bin/', "build/classes/java/") + } } } } diff --git a/src/main/java/com/reliancy/dbo/Action.java b/src/main/java/com/reliancy/dbo/Action.java index b6ea1d9..f7e19bb 100644 --- a/src/main/java/com/reliancy/dbo/Action.java +++ b/src/main/java/com/reliancy/dbo/Action.java @@ -1,35 +1,47 @@ package com.reliancy.dbo; import java.io.IOException; +import java.util.Collection; import java.util.Iterator; -import com.reliancy.util.CloseableIterator; - /** Description of a terminal operation with a slice of dbo objects as input or output. * This object is not just for reading but also bulk updating. * It will be used to describe a multi DBO read or write and to then also track results. + * At its core are action traits which are classes that define either loading,saving or deleting. + * The items field is a consumable object when consumed the action is done. + * So for loading we iterate once done it cannot be done again. Also when items are provided for saving + * once iterated over and saved they we done. */ -public class Action implements Iterable,CloseableIterator{ - public static enum Type{ - NONE,LOAD,SAVE,DELETE +public class Action implements Iterable,SiphonIterator{ + public static class Trait{ + public String toString(){return getClass().getSimpleName();} } + public static class Load extends Trait{ + int limit,offset; + Check filter; + } + public static class Save extends Trait{ + + } + public static class Delete extends Trait{ + Check filter; + } + Terminal terminal; - Type type; + Trait trait; Entity entity; Object[] params; - CloseableIterator items; - int limit,offset; - Condition filter; + SiphonIterator items; public Action(){ - type=Type.NONE; + trait=null; } - public Action(Type t){ - type=t; + public Action(Trait t){ + trait=t; } public Action(Terminal t){ terminal=t; - type=Type.NONE; + trait=null; } public Action execute() throws IOException{ return terminal.execute(this); @@ -42,11 +54,11 @@ public class Action implements Iterable,CloseableIterator{ this.terminal = terminal; return this; } - public Type getType() { - return type; + public Trait getTrait() { + return trait; } - public Action setType(Type type) { - this.type = type; + public Action setTrait(Trait t) { + this.trait = t; return this; } public Entity getEntity() { @@ -58,22 +70,27 @@ public class Action implements Iterable,CloseableIterator{ } public void clear(){ terminal=null; - type=Type.NONE; + trait=null; entity=null; setItems((DBO)null); } public Action load(Entity ent){ - type=Type.LOAD; + trait=new Load(); entity=ent; return this; } + public Action load(Class cls){ + trait=new Load(); + entity=Entity.recall(cls); + return this; + } public Action save(Entity ent){ - type=Type.SAVE; + trait=new Save(); entity=ent; return this; } public Action delete(Entity ent){ - type=Type.DELETE; + trait=new Delete(); entity=ent; return this; } @@ -81,10 +98,10 @@ public class Action implements Iterable,CloseableIterator{ params=p; return this; } - public Action setItems(DBO ...itms){ - CloseableIterator it=null; + public Action setItems(final DBO ...itms){ + SiphonIterator it=null; if(itms!=null){ - it=new CloseableIterator() { + it=new SiphonIterator() { private int index = 0; @Override public boolean hasNext() { @@ -101,7 +118,28 @@ public class Action implements Iterable,CloseableIterator{ } return setItems(it); } - public Action setItems(CloseableIterator itms){ + public Action setItems(final Collection itms){ + SiphonIterator it=null; + if(itms!=null){ + it=new SiphonIterator() { + private final Iterator str = itms.iterator(); + @Override + public boolean hasNext() { + return str.hasNext(); + } + @Override + public DBO next() { + return str.next(); + } + @Override + public void close() throws IOException { + } + }; + } + return setItems(it); + } + public Action setItems(SiphonIterator itms){ + if(items==itms) return this; if(items!=null){ try { items.close(); @@ -111,7 +149,7 @@ public class Action implements Iterable,CloseableIterator{ items=itms; return this; } - protected CloseableIterator getItems(){ + protected SiphonIterator getItems(){ return items; } @Override @@ -130,30 +168,51 @@ public class Action implements Iterable,CloseableIterator{ public void close() throws IOException { if(items!=null){ items.close(); + items=null; if(terminal!=null) terminal.end(this); } - items=null; } public Action limit(int max) { - limit=max; + ((Load)trait).limit=max; return this; } - public Action if_filter(Condition... c){ + public Action filterBy(Check... c){ + Check filter=null; if(c!=null){ - if(c.length>1) filter=Condition.and(c); + if(c.length>1) filter=Check.and(c); else filter=c[0]; + } + if(trait instanceof Load){ + ((Load)trait).filter=filter; + }else + if(trait instanceof Delete){ + ((Delete)trait).filter=filter; }else{ - filter=null; + throw new IllegalStateException("filtering not supported by trait:"+trait); } return this; } - public Action if_pk(Object[] id) { + public Check getFilter(){ + if(trait instanceof Load){ + return ((Load)trait).filter; + }else + if(trait instanceof Delete){ + return ((Delete)trait).filter; + }else{ + throw new IllegalStateException("filtering not supported by trait:"+trait); + } + } + public Action if_pk(Object... id) { Field pk=entity.getPk(); - return if_filter(Condition.eq(pk,id)); + return filterBy(pk.eq(id)); } public DBO first() { try{ - return items!=null?items.next():null; + if(this.hasNext()){ + return this.next(); + }else{ + return null; + } }finally{ clear(); } diff --git a/src/main/java/com/reliancy/dbo/Check.java b/src/main/java/com/reliancy/dbo/Check.java new file mode 100644 index 0000000..23867fc --- /dev/null +++ b/src/main/java/com/reliancy/dbo/Check.java @@ -0,0 +1,217 @@ +package com.reliancy.dbo; + +import java.util.Iterator; + +/** constraint on a field. + * conditions can be leafs or groups such as and,or,not + */ +public class Check implements Iterable { + public static abstract class Op{ + public abstract boolean met(Check c,Object val); + } + public static Op AND=new Op(){ + public String toString(){return "AND";} + public boolean met(Check c,Object val){ + return true; + } + }; + public static Op OR=new Op(){ + public String toString(){return "OR";} + public boolean met(Check c,Object val){ + return true; + } + }; + public static Op NOT=new Op(){ + public String toString(){return "NOT";} + public boolean met(Check c,Object val){ + return true; + } + }; + public static Op EQ=new Op(){ + public String toString(){return "=";} + public boolean met(Check c,Object val){ + return true; + } + }; + public static Op NEQ=new Op(){ + public String toString(){return "<>";} + public boolean met(Check c,Object val){ + return true; + } + }; + public static Op GT=new Op(){ + public String toString(){return ">";} + public boolean met(Check c,Object val){ + return true; + } + }; + public static Op GTE=new Op(){ + public String toString(){return ">=";} + public boolean met(Check c,Object val){ + return true; + } + }; + public static Op LT=new Op(){ + public String toString(){return "<";} + public boolean met(Check c,Object val){ + return true; + } + }; + public static Op LTE=new Op(){ + public String toString(){return "<=";} + public boolean met(Check c,Object val){ + return true; + } + }; + public static Op LIKE=new Op(){ + public String toString(){return "LIKE";} + public boolean met(Check c,Object val){ + return true; + } + }; + public static Op IN=new Op(){ + public String toString(){return "IN";} + public boolean met(Check c,Object val){ + return true; + } + }; + public static Op NOT_IN=new Op(){ + public String toString(){return "NOT IN";} + public boolean met(Check c,Object val){ + return true; + } + }; + public static class CheckIterator implements Iterator{ + final Check root; + Check cur; + int index; + public CheckIterator(Check ch){ + root=ch; + cur=root; + index=0; + } + @Override + public boolean hasNext() { + return cur.isLeaf()==false && index iterator() { + return new CheckIterator(this); + } + public int getChildCount(){ + return leaf?0:args.length; + } + public Check getChild(int index){ + return leaf?null:(Check)args[index]; + } + public Field getField(){ + return (Field)args[0]; + } + public Object getValue(){ + return (Object)args[1]; + } + public Check setValue(Object val){ + if(locked) throw new IllegalStateException("check value is locked"); + if(!leaf) throw new IllegalStateException("check is not a leaf"); + args[1]=val; + return this; + } + +} diff --git a/src/main/java/com/reliancy/dbo/Condition.java b/src/main/java/com/reliancy/dbo/Condition.java deleted file mode 100644 index d527327..0000000 --- a/src/main/java/com/reliancy/dbo/Condition.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.reliancy.dbo; - -/** constraint on a field. - * conditions can be leafs or groups such as and,or,not - */ -public class Condition { - public static abstract class Op{ - public abstract boolean met(Condition c); - } - public static Op AND=new Op(){ - public boolean met(Condition c){ - return true; - } - }; - public static Op OR=new Op(){ - public boolean met(Condition c){ - return true; - } - }; - public static Op NOT=new Op(){ - public boolean met(Condition c){ - return true; - } - }; - public static Op EQ=new Op(){ - public boolean met(Condition c){ - return true; - } - }; - public static Op NEQ=new Op(){ - public boolean met(Condition c){ - return true; - } - }; - public static Op GT=new Op(){ - public boolean met(Condition c){ - return true; - } - }; - public static Op GTE=new Op(){ - public boolean met(Condition c){ - return true; - } - }; - public static Op LT=new Op(){ - public boolean met(Condition c){ - return true; - } - }; - public static Op LTE=new Op(){ - public boolean met(Condition c){ - return true; - } - }; - public static Op LIKE=new Op(){ - public boolean met(Condition c){ - return true; - } - }; - public static Op IN=new Op(){ - public boolean met(Condition c){ - return true; - } - }; - public static Condition and(Condition... c) { - return new Condition(AND,c); - } - public static Condition eq(Field pk, Object... id) { - return new Condition(EQ,pk,id); - } - Op code; - Object[] args; - public Condition(Op code,Field f,Object val){ - this.code=code; - args=new Object[]{f,val}; - } - public Condition(Op code,Condition ... sub){ - this.code=code; - args=sub; - } -} diff --git a/src/main/java/com/reliancy/dbo/DBO.java b/src/main/java/com/reliancy/dbo/DBO.java index 3b27a47..cfc6fa4 100644 --- a/src/main/java/com/reliancy/dbo/DBO.java +++ b/src/main/java/com/reliancy/dbo/DBO.java @@ -1,34 +1,119 @@ package com.reliancy.dbo; +import java.io.IOException; + +import com.reliancy.rec.Hdr; +import com.reliancy.rec.JSON; +import com.reliancy.rec.Rec; +import com.reliancy.rec.Slot; /** Instance of an entity, usually a row in a table. * */ -public class DBO{ +public class DBO implements Rec{ public static enum Status{ NEW,USED,DELETED,COMPUTED } Terminal terminal; Entity type; Status status; + Object[] values; + public DBO() { + Class cls=this.getClass(); + if(cls!=DBO.class){ + Entity ent=Entity.recall(cls); + setType(ent); + } + status=Status.NEW; + } + @Override + public String toString(){ + try { + StringBuffer ret=new StringBuffer(); + JSON.writes(this,ret); + return ret.toString(); + } catch (IOException e) { + return e.toString(); + } } public Terminal getTerminal() { return terminal; } - public void setTerminal(Terminal terminal) { + public DBO setTerminal(Terminal terminal) { this.terminal = terminal; - } - public Entity getType() { - return type; - } - public void setType(Entity type) { - this.type = type; + return this; } public Status getStatus(){ return status; } - public void setStatus(Status s) { + public DBO setStatus(Status s) { this.status = s; + return this; } + public final Entity getType() { + return type; + } + public final DBO setType(Entity type) { + this.type = type; + if(type==null){ + values=null; + }else{ + values=new Object[type.count()]; + } + return this; + } + @Override + public Hdr meta() { + return type; + } + @Override + public int count() { + return values!=null?values.length:0; + } + @Override + public Rec set(int pos, Object val) { + if(pos<0) pos=count()+pos; + values[pos]=val; + return this; + } + @Override + public Object get(int pos) { + if(pos<0) pos=count()+pos; + return values[pos]; + } + @Override + public Rec add(Object val) { + throw new UnsupportedOperationException("dbo is not array"); + } + @Override + public Rec remove(int s) { + throw new UnsupportedOperationException("dbo is not array"); + } + @Override + public Rec set(Slot s, Object val) { + if(s==null) throw new IllegalArgumentException("invalid key provided"); + int index=s.getPosition(); // try slot position + //if(index<0) index=type.findSlot(s.getName());// fall back to search if slot not set + if(index<0){ + throw new IllegalArgumentException("invalid key provided:"+s.getName()); + }else{ + values[index]=val; + } + return this; + } + @Override + public Object get(Slot s, Object def) { + if(s==null) throw new IllegalArgumentException("invalid key provided"); + int index=s.getPosition(); // try slot position + //if(index<0) index=type.findSlot(s.getName());// fall back to search if slot not set + if(index<0) throw new IllegalArgumentException("invalid key provided:"+s.getName()); + Object ret=values[index]; + return ret==null?def:ret; + } + @Override + public Rec remove(Slot s) { + throw new UnsupportedOperationException("dbo is not resizable"); + } + } diff --git a/src/main/java/com/reliancy/dbo/Entity.java b/src/main/java/com/reliancy/dbo/Entity.java index 60a4b62..7c28de6 100644 --- a/src/main/java/com/reliancy/dbo/Entity.java +++ b/src/main/java/com/reliancy/dbo/Entity.java @@ -1,25 +1,52 @@ package com.reliancy.dbo; import java.util.HashMap; +import java.util.Iterator; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.Collection; import com.reliancy.rec.Hdr; +import com.reliancy.rec.Slot; /** Describes an object structure, usually a table. * */ public class Entity extends Hdr{ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public static @interface Info { + String name(); + } static final HashMap registry=new HashMap<>(); public static final void publish(Entity ent){ registry.put(ent.getName(),ent); + registry.put(ent.getId(),ent); } public static final void retract(Entity ent){ - registry.values().remove(ent); + if(ent==null) return; + Collection vals=registry.values(); + while(vals.remove(ent)){} + } + public static final void retract(Class cls){ + Entity ent=recall(cls.getSimpleName()); + if(ent!=null){ + retract(ent); + } } public static final Entity recall(String name){ return registry.get(name); } - public static final Entity recall(Class cls){ - return recall(cls.getSimpleName()); + + public static final Entity recall(Class cls){ + Entity ent=recall(cls.getSimpleName()); + if(ent==null){ + ent=publish(cls); + } + return ent; } /** * this method will analyze a DBO class and forumate an Entity object out of it. @@ -27,15 +54,124 @@ public class Entity extends Hdr{ * @return */ public static final Entity publish(Class cls){ - return null; + Entity ret=registry.get(cls.getSimpleName()); + if(ret!=null) return ret; + //System.out.println("Analyzing:"+cls); + Class base=cls.getSuperclass(); + Entity base_ent=null; + int position0=0; + if(base!=null && base!=DBO.class){ + base_ent=publish(base); + position0=base_ent.count(); + } + java.lang.reflect.Field[] declaredFields = cls.getDeclaredFields(); + ArrayList slots=new ArrayList<>(); + for (java.lang.reflect.Field field : declaredFields) { + if (!java.lang.reflect.Modifier.isStatic(field.getModifiers())) { + continue; + } + try { + String sf_name=field.getName(); + Field slot=(Field) field.get(cls); + slot.setId(sf_name); + slot.setPosition(position0+slots.size()); + slots.add(slot); + //System.out.println(sf_name+":"+slot+" atpos:"+slot.getPosition()); + } catch (Exception e) { + } + } + Info info=cls.getAnnotation(Info.class); + ret=new Entity(info!=null?info.name():cls.getSimpleName()).setId(cls.getSimpleName()); + ret.setBase(base_ent); + ret.setType(cls); + ret.getOwnSlots().addAll(slots); + publish(ret); + return ret; } Entity base; - String dbName; + String id; Field pk; public Entity(String name) { super(name); } + @Override + public Slot makeSlot(String name){ + return new Field(name); + } + @Override + public Iterator iterator(int offset){ + if(offset>0) throw new IllegalArgumentException("Offset not supported"); + final Entity ent=this; + return new Iterator(){ + final FieldSlice slice=new FieldSlice(ent).including(Field.FLAG_STORABLE); + @Override + public boolean hasNext() { + return slice.hasNext(); + } + @Override + public Slot next() { + return slice.next(); + } + + }; + } + @Override + public int count(){ + return super.count()+(base!=null?base.count():0); + } + /** + * gets a slot which could be here or in base. + */ + @Override + public Slot getSlot(int pos){ + if(base!=null){ // we got base + int ofs=base.count(); + if(pos cls=getType(); + DBO ret=(DBO) cls.newInstance(); + ret.setType(this).setTerminal(t).setStatus(DBO.Status.NEW); + return ret; + } } diff --git a/src/main/java/com/reliancy/dbo/Field.java b/src/main/java/com/reliancy/dbo/Field.java index b2a8239..1afff90 100644 --- a/src/main/java/com/reliancy/dbo/Field.java +++ b/src/main/java/com/reliancy/dbo/Field.java @@ -1,16 +1,101 @@ package com.reliancy.dbo; +import java.math.BigDecimal; +import java.sql.Date; +import java.sql.Timestamp; + import com.reliancy.rec.Slot; /** * Description of a column or property. */ public class Field extends Slot { - + public static Field Int(String name){ + return new Field(name,Integer.class); + } + public static Field Str(String name){ + return new Field(name,String.class); + } + public static Field Bool(String name){ + return new Field(name,Boolean.class); + } + public static Field Float(String name){ + return new Field(name,Float.class); + } + public static Field Num(String name){ + return new Field(name,BigDecimal.class); + } + public static Field Date(String name){ + return new Field(name,Date.class); + } + public static Field DateTime(String name){ + return new Field(name,Timestamp.class); + } + public static final int FLAG_PK =0x0100; + public static final int FLAG_AUTOINC =0x0200; + String id; + String typeParams; public Field(String name) { super(name); + this.raiseFlags(Field.FLAG_STORABLE); } public Field(String name,Class typ) { super(name,typ); + this.raiseFlags(Field.FLAG_STORABLE); + } + @Override + public boolean equals(String str){ + return super.equals(str) || (id!=null && id.equalsIgnoreCase(str)); + } + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + public boolean isPk() { + return checkFlags(FLAG_PK); + } + public Field setPk(boolean pk) { + if(pk) raiseFlags(FLAG_PK); else clearFlags(FLAG_PK); + return this; + } + public boolean isAutoIncrement() { + return checkFlags(FLAG_AUTOINC); + } + public Field setAutoIncrement(boolean pk) { + if(pk) raiseFlags(FLAG_AUTOINC); else clearFlags(FLAG_AUTOINC); + return this; + } + public String getTypeParams() { + return typeParams; + } + public Field setTypeParams(String p) { + typeParams=p; + return this; + } + public Check eq(Object... val) { + return Check.eq(this,val); + } + public Check neq(Object... val) { + return Check.neq(this,val); + } + public Check gt(Object... val) { + return Check.gt(this,val); + } + public Check gte(Object... val) { + return Check.gte(this,val); + } + public Check lt(Object... val) { + return Check.lt(this,val); + } + public Check lte(Object... val) { + return Check.lte(this,val); + } + public Check like(Object... val) { + return Check.like(this,val); + } + public Check in(Object... val) { + return Check.in(this,val); } } diff --git a/src/main/java/com/reliancy/dbo/FieldSlice.java b/src/main/java/com/reliancy/dbo/FieldSlice.java new file mode 100644 index 0000000..6f1d2b7 --- /dev/null +++ b/src/main/java/com/reliancy/dbo/FieldSlice.java @@ -0,0 +1,102 @@ +package com.reliancy.dbo; +import java.util.Iterator; +import java.util.List; + +/** Field iterator matching flags over entity hierarchy. + * + */public class FieldSlice implements Iterator,Iterable{ + protected final Entity entity; + protected FieldSlice sup; + protected int includeMask; + protected int excludeMask; + protected int raw_index; + protected Field next_field; + protected int next_index; + protected Entity next_entity; + public FieldSlice(Entity ent){ + entity=ent; + if(entity.getBase()!=null){ + sup=new FieldSlice(ent.getBase()); + }else{ + sup=null; + } + raw_index=-1; + next_index=-1; + } + public FieldSlice including(int mask){ + includeMask=mask; + if(sup!=null) sup.including(mask); + return this; + } + public FieldSlice excluding(int mask){ + excludeMask=mask; + if(sup!=null) sup.excluding(mask); + return this; + } + /** we add rewind capability to allow reuse of same fieldslice. + * i.e we use it to generate recipe, then later to properly enumerate values. + */ + public FieldSlice rewind(){ + raw_index=-1; + next_index=-1; + next_field=null; + next_entity=null; + if(sup!=null) sup.rewind(); + return this; + } + // search for next valid field + public final Field findNext(){ + List local=entity.getOwnSlots(); + if(raw_index>=local.size()) return null; // we have exhausted local supply + next_field=null; // clear prev result + // search at base + if(sup!=null && sup.hasNext()){ + next_field=sup.next(); + next_index++; + next_entity=sup.nextEntity(); + return next_field; + } + next_entity=entity; + // now search locally + for(raw_index=raw_index+1;raw_index iterator() { + return this; + } + @Override + public boolean hasNext() { + Field next=findNext(); + return next!=null; + } + + @Override + public Field next() { + return next_field; + } + + public int nextIndex(){ + return next_field!=null?next_index:-1; + } + public Entity nextEntity(){ + return next_entity; + } + public DBO makeRecord() throws InstantiationException, IllegalAccessException{ + return entity.newInstance(); + } + public void writeRecord(DBO rec,Object val){ + rec.set(next_field, val); + } + public Object readRecord(DBO rec,Object def){ + return rec.get(next_field, def); + } +} diff --git a/src/main/java/com/reliancy/dbo/SQL.java b/src/main/java/com/reliancy/dbo/SQL.java new file mode 100644 index 0000000..89baa8a --- /dev/null +++ b/src/main/java/com/reliancy/dbo/SQL.java @@ -0,0 +1,263 @@ +package com.reliancy.dbo; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.reliancy.util.Handy; + +public final class SQL implements Appendable{ + public final static Object NULL=new Object(); + public final static String WS=" "; + public final static String SELECT="SELECT"; + public final static String INSERT="INSERT INTO"; + public final static String UPDATE="UPDATE"; + public final static String DELETE="DELETE"; + public final static String FROM="FROM"; + public final static String INNER_JOIN="INNER JOIN"; + public final static String ON="ON"; + public final static String SET="SET"; + public final static String WHERE="WHERE"; + + final StringBuffer buffer=new StringBuffer(); + final SQLTerminal terminal; + final String ql,qr; + final HashMap entAlias=new HashMap<>(); + + public SQL(SQLTerminal terminal){ + this.terminal=terminal; + ql=terminal!=null?terminal.getQuoteLeft():"\""; + qr=terminal!=null?terminal.getQuoteRight():"\""; + } + @Override + public String toString(){ + return buffer.toString(); + } + @Override + public final SQL append(CharSequence csq){ + buffer.append(csq); + return this; + } + @Override + public final SQL append(CharSequence csq, int start, int end){ + buffer.append(csq,start,end); + return this; + } + @Override + public final SQL append(char c){ + buffer.append(c); + return this; + } + public final SQL select(){ + append(SELECT); + return this; + } + public final SQL insert(){ + append(INSERT); + return this; + } + public final SQL update(){ + append(UPDATE); + return this; + } + public final SQL delete(){ + append(DELETE); + return this; + } + public final SQL from(){ + append(WS).append(FROM).append(WS); + return this; + } + public final SQL inner_join(){ + append(WS).append(INNER_JOIN).append(WS); + return this; + } + public final SQL on(){ + append(WS).append(ON).append(WS); + return this; + } + public final String wrap(String id){ + if(id.startsWith(ql) && id.endsWith(qr)){ + return id; + }else{ + return ql+id.replace(".",qr+"."+ql)+qr; + } + } + public final SQL id(String id){ + if(id.startsWith(ql) && id.endsWith(qr)){ + append(id); + }else{ + append(ql).append(id.replace(".",qr+"."+ql)).append(qr); + } + return this; + } + public final String getAlias(Entity e){ + String eAlias=entAlias.get(e); + if(eAlias!=null) return eAlias; + eAlias="e"+entAlias.size(); + entAlias.put(e,eAlias); + return eAlias; + } + public final SQL select(Entity ent,FieldSlice fit){ + entAlias.clear();; + select(); + while(fit.hasNext()){ + int index=fit.nextIndex(); + Field f=fit.next(); + Entity e=fit.nextEntity(); + String alias=getAlias(e); + //System.out.println("It:"+index+":/"+f+"/"+e+"/"+alias); + append(index==0?" ":","); + append(alias).append(".").id(f.getName()); + } + from(); + String eAlias=getAlias(ent); + id(ent.getName()).append(" ").append(eAlias); + for(Entity b=ent.getBase();b!=null;b=b.getBase()){ + String bAlias=getAlias(b); + inner_join(); + id(b.getName()).append(" ").append(bAlias); + on(); + Field bPk=b.getPk(); + Field ePk=ent.getPk(); + append(eAlias).append(".").id(ePk.getName()); + append("="); + append(bAlias).append(".").id(bPk.getName()); + } + return this; + } + public final SQL where(){ + append(WS).append(WHERE).append(WS); + return this; + } + public final SQL where(Check filter) { + append(WS).append(WHERE).append(WS).check(filter); + return this; + } + /// using entalias locate field entity and its prefix + public final String getFieldPrefix(Field f){ + for(Map.Entry e:entAlias.entrySet()){ + Entity ent=e.getKey(); + String prefix=e.getValue(); + if(ent.isOwned(f)) return prefix+"."; + } + return ""; + } + public final SQL check(Check filter) { + if(filter.isLeaf()){ + Check.Op op=filter.getCode(); + Field field=filter.getField(); + String fname=wrap(filter.getField().getName()); + String opname=op.toString(); + String arg="?"; + Object val=filter.getValue(); + if(op==Check.LIKE && terminal!=null && terminal.getProtocol().contains(":postgre")){ + opname="ILIKE"; + } + if(op==Check.IN){ + arg="("+Handy.toString(val)+")"; + } + if(Handy.isEmpty(val)){ + // if not value then shortcuircuid condition + fname="1"; + opname="="; + arg="1"; + } + if(val==NULL){ + arg="NULL"; + if(op==Check.EQ) opname="IS"; + if(op==Check.NEQ) opname="IS NOT"; + } + append("("); + String fprefix=getFieldPrefix(field); + append(fprefix).append(fname).append(WS).append(opname).append(WS).append(arg); + append(")"); + }else{ + Check.Op op=filter.getCode(); + String delim=op.toString(); + if(op==Check.NOT){ + append(delim).append("(").check(filter.getChild(0)).append(")"); + }else{ + append("("); + for(int i=0;i0) append(WS).append(delim).append(WS); + check(filter.getChild(i)); + } + append(")"); + } + } + return this; + } + /** fills params list with non-trivial parameters. + * we place this method here to be as close as possible to the one which generates the sql code. + * check and check_export must be in synch. + * @param filter + * @param params + */ + public final void check_export(Check filter,List params) { + if(filter.isLeaf()){ + Check.Op op=filter.getCode(); + Object val=filter.getValue(); + if(Handy.isEmpty(val) || val==NULL || op==Check.IN) return; // skip over empty or NULL values + params.add(val); + }else{ + for(Check ch:filter) check_export(ch,params); + } + } + /** fills check values from dbo record where equal and not-equal are used. + * we place this method here to be as close as possible to the one which generates the sql code. + * check and check_import must be in synch. + * @param filter + * @param params + */ + public final void check_import(Check filter,DBO rec) { + if(filter.isLeaf()){ + if(filter.isLocked()) return; // no import on locked condition + Check.Op op=filter.getCode(); + if(op!=Check.EQ && op!=Check.NEQ) return; // skip over all conditions except = and <> + Field f=filter.getField(); + Object val=f.get(rec,null); + filter.setValue(val); + }else{ + for(Check ch:filter) check_import(ch,rec); + } + } + public final SQL insert(Entity entity,List supplied){ + insert(); + append(SQL.WS).id(entity.getName()).append(" ("); + StringBuffer ext=new StringBuffer(); + String delim=""; + Field pk=entity.getPk(); + if(!entity.isOwned(pk)){ + append(delim).id(pk.getName()); + ext.append(delim).append("?"); + delim=","; + } + for(int index=0;index0) delim=","; + append(delim).id(f.getName()); + ext.append(delim).append("?"); + } + append(") VALUES (").append(ext).append(")"); + return this; + } + public final SQL update(Entity entity,List supplied){ + update(); + append(SQL.WS).id(entity.getName()).append(" SET "); + for(int index=0;index params; + protected Check filter; + protected Connection external; + protected PreparedStatement deleteStmt; + protected int itemsDeleted; + protected Exception error; + + public SQLCleaner(Entity ent,SQLTerminal t) { + entity=ent; + terminal=t; + base=(entity.getBase()!=null)?new SQLCleaner(entity.getBase(),t):null; + sql=new SQL(terminal); + params=new ArrayList<>(); + } + public SQL compileRecipe(){ + if(filter==null){ + // no filter we go with PK + Field pk=entity.getPk(); + filter=pk.eq("?"); + } + sql.delete(entity).where(filter); + return sql; + } + public boolean isLinkExternal(){ + return external!=null; + } + public SQLCleaner setExternalLink(Connection link){ + external=link; + return this; + } + protected Connection getExternalLink(){ + return external; + } + protected Connection getInternalLink(){ + try{ + if(deleteStmt!=null) return deleteStmt.getConnection(); + }catch(SQLException ex){ + } + return null; + } + public SQLCleaner open() throws SQLException{ + return open(null); + } + public SQLCleaner open(Check where) throws SQLException{ + this.filter=where; + Connection link=isLinkExternal()?getExternalLink():terminal.getConnection(); + if(base!=null) base.setExternalLink(link).open(filter); // definitely external link for base + SQL delSQL=compileRecipe(); + //System.out.println("DEL:"+delSQL+"/"+filter); + deleteStmt=link.prepareStatement(delSQL.toString()); + return this; + } + @Override + public void close() throws IOException{ + if(base!=null) base.close(); // since link is external it will not close link just the rest + Connection link=getInternalLink(); + if(deleteStmt!=null){ + try{ + deleteStmt.close(); + }catch(SQLException ex){ + if(error==null) error=ex; + } + } + try{ + if(link!=null && !isLinkExternal()) link.close(); + external=null; + }catch(SQLException ex){ + if(error==null) error=ex; + } + if(error!=null){ + if(error instanceof IOException) throw (IOException)error; + else throw new IOException(error); + } + + } + public void flush(Iterator items) throws SQLException { + Connection link=getInternalLink(); + boolean autocommited=link.getAutoCommit(); + try{ + link.setAutoCommit(false); + if(items==null){ // deleting by filter + throw new SQLException("delete by filter not implemented yet"); + // we would need to leave the primary filter + // we would use filter in an ID in (SUBQUERY) + // this is because filter could reference all entities and we have inheritance so multiple + // we would generate a select statement with filter and selecting only ID + }else{ // deleting by incoming records + while(items.hasNext()){ + DBO rec=items.next(); + deleteRecord(rec); + } + } + if(!link.getAutoCommit()){ + link.commit(); + } + }catch(SQLException ex){ + if(!link.getAutoCommit()){ + link.rollback(); + } + throw ex; + }finally{ + link.setAutoCommit(autocommited); + } + } + /** + * This calls one delete. It can and is called from outside in case of nesting when link is external. + * @param rec + * @throws SQLException + */ + public boolean deleteRecord(DBO rec) throws SQLException{ + if(rec==null) return false; + if(base!=null) base.deleteRecord(rec); // save the superclass first + sql.check_import(filter,rec); // get values from dbo + params.clear(); + sql.check_export(filter,params); // move them into params + for(int pindex=0;pindex0; + } +} diff --git a/src/main/java/com/reliancy/dbo/SQLReader.java b/src/main/java/com/reliancy/dbo/SQLReader.java new file mode 100644 index 0000000..445a05e --- /dev/null +++ b/src/main/java/com/reliancy/dbo/SQLReader.java @@ -0,0 +1,127 @@ +package com.reliancy.dbo; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import com.reliancy.dbo.Action.Load; + + +/** SQLIterator will delay closing a connection and will iterate over result set. + * + */ +public class SQLReader implements SiphonIterator{ + protected final Entity entity; + protected final SQLTerminal terminal; + protected final SQL sql; + protected FieldSlice slice; + protected ResultSet result; + protected Exception error; + + public SQLReader(Entity ent,SQLTerminal t) { + this.entity=ent; + terminal=t; + // slice controls sql fields but also lets us correctly import values later + slice=new FieldSlice(entity).including(Field.FLAG_STORABLE); + sql=new SQL(terminal); + } + public SQLReader open() throws SQLException{ + return open(null); + } + public SQLReader open(Action action) throws SQLException{ + error=null; + if(action==null){ + sql.select(entity,slice); // simple case + }else{ + compileRecipe(action); // complete case + } + //System.out.println("SQL:"+sql); + Connection link=terminal.getConnection(); + PreparedStatement prep=link.prepareStatement(sql.toString()); + if(action!=null){ + Load tr=(Load) action.getTrait(); + if(tr.filter!=null){ + ArrayList params=new ArrayList<>(); + sql.check_export(tr.filter, params); + for(int pindex=0;pindex> sql2java=new HashMap<>(); + final HashMap,Integer> java2sql=new HashMap<>(); + public Map,Integer> getJava2SQL(){ + if(!java2sql.isEmpty()) return java2sql; + String protocol=url.getProtocol(); + java2sql.put(java.math.BigDecimal.class,Types.DECIMAL); + java2sql.put(java.math.BigInteger.class,Types.DECIMAL); + java2sql.put(Boolean.class,protocol.contains(":oracle")?Types.INTEGER:Types.BOOLEAN); + java2sql.put(Byte.class,Types.TINYINT); + java2sql.put(Short.class,Types.SMALLINT); + java2sql.put(Integer.class,Types.INTEGER); + java2sql.put(Long.class,Types.BIGINT); + java2sql.put(Float.class,Types.FLOAT); + java2sql.put(Double.class,Types.DOUBLE); + java2sql.put(byte[].class,Types.VARBINARY); + java2sql.put(Blob.class,Types.BLOB); + java2sql.put(char[].class,Types.VARCHAR); + java2sql.put(String.class,Types.VARCHAR); + java2sql.put(StringBuffer.class,Types.VARCHAR); + java2sql.put(Clob.class,Types.CLOB); + java2sql.put(java.sql.Date.class,Types.DATE); + java2sql.put(java.sql.Time.class,Types.TIME); + java2sql.put(java.sql.Timestamp.class,Types.TIMESTAMP); + java2sql.put(Array.class,Types.ARRAY); + return java2sql; + } + public Map> getSQL2Java(){ + if(!sql2java.isEmpty()) return sql2java; + //String protocol=url.getProtocol(); + sql2java.put(Types.NUMERIC,java.math.BigDecimal.class); + sql2java.put(Types.DECIMAL,java.math.BigDecimal.class); + sql2java.put(Types.BIT,Boolean.class); + sql2java.put(Types.BOOLEAN,Boolean.class); + sql2java.put(Types.TINYINT,Byte.class); + sql2java.put(Types.SMALLINT,Short.class); + sql2java.put(Types.INTEGER,Integer.class); + sql2java.put(Types.BIGINT,Long.class); + sql2java.put(Types.REAL,Float.class); + sql2java.put(Types.FLOAT,Float.class); + sql2java.put(Types.DOUBLE,Double.class); + sql2java.put(Types.BINARY,byte[].class); + sql2java.put(Types.VARBINARY,byte[].class); + sql2java.put(Types.LONGVARBINARY,byte[].class); + sql2java.put(Types.CHAR,String.class); + sql2java.put(Types.NCHAR,String.class); + sql2java.put(Types.VARCHAR,String.class); + sql2java.put(Types.NVARCHAR,String.class); + sql2java.put(Types.LONGVARCHAR,String.class); + sql2java.put(Types.LONGNVARCHAR,String.class); + sql2java.put(Types.DATE,java.sql.Date.class); + sql2java.put(Types.TIME,java.sql.Time.class); + sql2java.put(Types.TIMESTAMP,java.sql.Timestamp.class); + sql2java.put(Types.BLOB,byte[].class); + sql2java.put(Types.CLOB,char[].class); + sql2java.put(Types.ARRAY,java.sql.Array.class); + sql2java.put(Types.JAVA_OBJECT,Object.class); + return sql2java; + } + /** + * Returns back java class for given id and or name. + * The name is not used in default implementation. + * @param typeid + */ + public Class getJavaType(int typeid) { + Class ret=getSQL2Java().get(typeid); + return ret; + } + /** + * This method will correct cases when sqltype is varchar (12) but type name is date or similar. + * @param sqltype + * @param type_name + * @return tries to promote sqltype given type name to something more specific. + */ + public int getTypeId(int sqltype,String type_name){ + if(type_name==null) return sqltype; + type_name=type_name.toLowerCase(); + if(sqltype==Types.VARCHAR || sqltype==Types.CHAR){ + if(type_name.equals("date")) sqltype=Types.DATE; + if(type_name.equals("time")) sqltype=Types.TIME; + if(type_name.equals("datetime")) sqltype=Types.TIMESTAMP; + } + return sqltype; + } + + /** + * @param cls + * @param createParams + * @return SQL type given java class and create params + */ + public int getTypeId(Class cls,String createParams){ + int ret=getJava2SQL().get(cls); + return ret; + } + public String getTypeName(Class cls,String createParams){ + int id=this.getTypeId(cls, createParams); + String ret = JDBCType.valueOf(id).getName(); + if(ret==null) return null; + String protocol=url.getProtocol(); + if(protocol.contains(":sqlserver")){ + if("boolean".equalsIgnoreCase(ret)) ret="BIT"; + if("timestamp".equalsIgnoreCase(ret)) ret="DATETIME"; + if("double".equalsIgnoreCase(ret)) ret="float"; + if("float".equalsIgnoreCase(ret)) ret="real"; + } + if(protocol.contains(":postgre")){ + if("varbinary".equalsIgnoreCase(ret)) ret="bytea"; + if("double".equalsIgnoreCase(ret)) ret="double precision"; + } + if("varchar".equalsIgnoreCase(ret) && (createParams!=null && !createParams.isEmpty())){ + long size=Long.parseLong(createParams); + if(protocol.contains(":sqlserver")) ret=size>8000?ret.concat("(").concat("MAX").concat(")"):ret.concat("(").concat(String.valueOf(size)).concat(")"); + else if(protocol.contains(":oracle")) ret=size>2000?"CLOB":ret.concat("(").concat(String.valueOf(size)).concat(")"); + else if(protocol.contains(":mysql")) ret=size>Character.MAX_VALUE?"TEXT":ret.concat("(").concat(String.valueOf(size)).concat(")"); + else if(protocol.contains(":h2")) ret=size>Integer.MAX_VALUE?"CLOB":ret.concat("(").concat(String.valueOf(size)).concat(")"); + else if(protocol.contains(":postgre")) ret=size>Character.MAX_VALUE?"TEXT":ret.concat("(").concat(String.valueOf(size)).concat(")"); + else ret=(size>Character.MAX_VALUE)?"CLOB":ret.concat("(").concat(String.valueOf(size)).concat(")"); + } + String args=null; + if(ret.indexOf('(')==-1 && createParams!=null && !createParams.isEmpty()){ + if("decimal".equalsIgnoreCase(ret)) args=createParams; + if("numeric".equalsIgnoreCase(ret)) args=createParams; + } + if(args!=null){ + ret=ret.concat("(").concat(args).concat(")"); + } + return ret; + + } } diff --git a/src/main/java/com/reliancy/dbo/SQLWriter.java b/src/main/java/com/reliancy/dbo/SQLWriter.java new file mode 100644 index 0000000..38fa709 --- /dev/null +++ b/src/main/java/com/reliancy/dbo/SQLWriter.java @@ -0,0 +1,208 @@ +package com.reliancy.dbo; + +import java.io.Closeable; +import java.io.IOException; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; + +import com.reliancy.util.Handy; + +/** Helper object which impleents DBO saving. It manages the recipe and the prepared statmenet. + * Also keeps track of which fields are sent down to DB. + * The writer does the work in flush method during which it exhausts the items. While in flush it + * will disable autocommit and enable it at the end. We could call flush multiple times to send multiple + * batches down to DB. + * Initially we would ctor with Action object but actually we need to generate writes for different entities + * especially in inheritance cases. + */ +public class SQLWriter implements Closeable{ + protected final Entity entity; + protected final SQLTerminal terminal; + protected final SQLWriter base; /// used for nesting + protected final ArrayList supplied=new ArrayList(); + protected final ArrayList generated=new ArrayList(); + protected String insertSQL; + protected String updateSQL; + protected Connection external; + protected PreparedStatement insertStmt; + protected PreparedStatement updateStmt; + protected int itemsInserted; + protected int itemsUpdated; + protected Exception error; + + public SQLWriter(Entity ent,SQLTerminal t) { + entity=ent; + terminal=t; + base=(entity.getBase()!=null)?new SQLWriter(entity.getBase(),t):null; + // we select proper fields for this entity + FieldSlice slice=new FieldSlice(entity).including(Field.FLAG_STORABLE); // includes all even autoincrement + while(slice.hasNext()){ + Field f=slice.next(); + Entity e=slice.nextEntity(); + if(e!=entity) continue; // skip if not part of this entity + if(f.isAutoIncrement()){ + generated.add(f); + }else{ + supplied.add(f); + } + } + } + public String compileInsertRecipe(){ + if(insertSQL!=null) return insertSQL; + SQL buf=new SQL(terminal); + buf.insert(entity,supplied); + insertSQL=buf.toString(); + return insertSQL; + } + public String compileUpdateRecipe(){ + if(updateSQL!=null) return updateSQL; + SQL buf=new SQL(terminal); + buf.update(entity,supplied); + updateSQL=buf.toString(); + return updateSQL; + } + public boolean isLinkExternal(){ + return external!=null; + } + public SQLWriter setExternalLink(Connection link){ + external=link; + return this; + } + protected Connection getExternalLink(){ + return external; + } + protected Connection getInternalLink(){ + try{ + if(insertStmt!=null) return insertStmt.getConnection(); + if(updateStmt!=null) return updateStmt.getConnection(); + }catch(SQLException ex){ + } + return null; + } + public SQLWriter open() throws SQLException{ + Connection link=isLinkExternal()?getExternalLink():terminal.getConnection(); + if(base!=null) base.setExternalLink(link).open(); // definitely external link for base + String inSql=compileInsertRecipe(); + String upSql=compileUpdateRecipe(); + //System.out.println("INS:"+inSql); + //System.out.println("UPD:"+upSql); + String[] genkeys=new String[generated.size()]; + for(int i=0;i items) throws SQLException { + Connection link=isLinkExternal()?getExternalLink():getInternalLink(); + boolean autocommited=link.getAutoCommit(); + try{ + link.setAutoCommit(false); + while(items.hasNext()){ + DBO rec=items.next(); + writeRecord(rec); + } + if(!link.getAutoCommit()){ + link.commit(); + } + }catch(SQLException ex){ + if(!link.getAutoCommit()){ + link.rollback(); + } + throw ex; + }finally{ + link.setAutoCommit(autocommited); + } + } + /** + * This calls one update/insert. It can and is called from outside in case of nesting when link is external. + * @param rec + * @throws SQLException + */ + public boolean writeRecord(DBO rec) throws SQLException{ + if(base!=null) base.writeRecord(rec); // save the superclass first + // select mode + int pindex=0; + Field pk=entity.getPk(); + boolean pk_owned=entity.isOwned(pk); + PreparedStatement stmt=null; + if(rec.getStatus()==DBO.Status.NEW){ + stmt=insertStmt; + // need to inject pk here is not owned + if(!pk_owned) stmt.setObject(++pindex,pk.get(rec,null),terminal.getTypeId(pk.getType(),pk.getTypeParams())); + } + if(rec.getStatus()==DBO.Status.USED){ + stmt=updateStmt; + // update has a pk condition for sure + stmt.setObject(supplied.size()+1,pk.get(rec,null),terminal.getTypeId(pk.getType(),pk.getTypeParams())); + } + if(stmt==null) return false; + // copy values + for(int index=0;index0 && !generated.isEmpty()){ + try (ResultSet keys = stmt.getGeneratedKeys()) { + if(keys.next()){ + for(int i=0;i0; + } +} diff --git a/src/main/java/com/reliancy/dbo/SiphonIterator.java b/src/main/java/com/reliancy/dbo/SiphonIterator.java new file mode 100644 index 0000000..8f0276e --- /dev/null +++ b/src/main/java/com/reliancy/dbo/SiphonIterator.java @@ -0,0 +1,9 @@ +package com.reliancy.dbo; + +import java.io.Closeable; +import java.util.Iterator; +/** Iterator interface suitable for dbo resultsets. + * + */ +public interface SiphonIterator extends Iterator, Closeable { +} \ No newline at end of file diff --git a/src/main/java/com/reliancy/dbo/Terminal.java b/src/main/java/com/reliancy/dbo/Terminal.java index 8353c67..8154726 100644 --- a/src/main/java/com/reliancy/dbo/Terminal.java +++ b/src/main/java/com/reliancy/dbo/Terminal.java @@ -25,6 +25,13 @@ public interface Terminal { public default Terminal meta(Entity ent){ return null; } + public default T load(Class cls,Object...id) throws IOException { + Entity ent=Entity.recall(cls); + String sig="/"+ent.getName()+"/load"; + try(Action act=begin(sig).load(ent).limit(1).if_pk(id).execute()){ + return (T)act.first(); + } + } public default DBO load(Entity ent,Object...id) throws IOException { String sig="/"+ent.getName()+"/load"; try(Action act=begin(sig).load(ent).limit(1).if_pk(id).execute()){ @@ -39,6 +46,7 @@ public interface Terminal { } } public default boolean delete(DBO rec) throws IOException { + if(rec==null) return false; Entity ent=rec.getType(); String sig="/"+ent.getName()+"/delete"; try(Action act=begin(sig).delete(ent).setItems(rec).execute()){ diff --git a/src/main/java/com/reliancy/jabba/HTTP.java b/src/main/java/com/reliancy/jabba/HTTP.java index e0bfab3..4ec93c7 100644 --- a/src/main/java/com/reliancy/jabba/HTTP.java +++ b/src/main/java/com/reliancy/jabba/HTTP.java @@ -47,8 +47,13 @@ public final class HTTP { public static String guess_mime(Object ret) { if(ret instanceof CharSequence){ CharSequence retstr=(CharSequence)ret; - if(retstr.length()>0 && retstr.charAt(0)=='<') return "text/html"; - if(retstr.length()>0 && "{[".indexOf(retstr.charAt(0))!=-1) return "application/json"; + for(int index=0;indexHello from code: {{name}}! -{% endblock %} diff --git a/src/main/java/com/reliancy/rec/Hdr.java b/src/main/java/com/reliancy/rec/Hdr.java index 603740b..b97e8c3 100644 --- a/src/main/java/com/reliancy/rec/Hdr.java +++ b/src/main/java/com/reliancy/rec/Hdr.java @@ -1,16 +1,23 @@ package com.reliancy.rec; import java.util.ArrayList; -import java.util.ListIterator; +import java.util.Iterator; +import java.util.List; /** Base class of meta objects. * We use it to describe certain meta information. We derive from it Slot. - * We define keys list of slots on the header level to describe slots. + * We define keys list of slots on the header level to describe sub-slots. + * + * This class describes structure of Fields or Entities via the keys array of slots. + * Additionally we provide a number of methods to locate, set or get or remove or add slots. + * However slots could reside in other places such as base classes and so getOwnSlots will return a + * bare list of slots in this object while all other methods will take into account other sources. + * We do this to stay consistent at Rec level when Hdr inheritance comes into play. */ public class Hdr { public static final int FLAG_ARRAY =0x0001; public static final int FLAG_CHANGED =0x0002; - public static final int FLAG_HIDDEN =0x0004; + public static final int FLAG_STORABLE =0x0004; public static final int FLAG_LOCKED =0x0008; int flags; String name; @@ -30,8 +37,11 @@ public class Hdr { @Override public String toString(){ StringBuilder ret=new StringBuilder(); - ret.append("{").append("flags:").append(flags).append(",name:").append(name); - ret.append(",dim:").append(keys.size()).append("}"); + ret.append(name).append(":"); + ret.append("{") + .append("flags:").append(flags) + .append(",dim:").append(count()) + .append("}"); return ret.toString(); } @@ -53,6 +63,9 @@ public class Hdr { public void setType(Class type) { this.type = type; } + public int getFlags(){ + return flags; + } public Hdr raiseFlags(int f){ flags|=f; return this; @@ -67,36 +80,51 @@ public class Hdr { public T castAs(Class clazz){ return clazz.cast(this); } - public int findSlot(String name){ - return findSlot(name,0); + public List getOwnSlots(){ + return keys; } - public int findSlot(String name,int ofs){ - ListIterator it=keys.listIterator(ofs); + public boolean isOwned(Slot s){ + return keys.contains(s); + } + public Iterator iterator(int offset){ + return keys.listIterator(offset); + } + public int indexOf(String name){ + return indexOf(name,0); + } + public int indexOf(String name,int ofs){ + Iterator it=iterator(ofs); + int index=-1; while(it.hasNext()){ - int index=it.nextIndex(); + index+=1; Slot e=it.next(); - if(e.getName().equalsIgnoreCase(name)) return index; + //if(e.getName().equalsIgnoreCase(name)) return index; + if(e.equals(name)) return index; } return -1; } - public int findSlot(Slot s,int ofs){ - ListIterator it=keys.listIterator(ofs); + public int indexOf(Slot s,int ofs){ + Iterator it=iterator(ofs); + int index=-1; while(it.hasNext()){ - int index=it.nextIndex(); + index+=1; Slot e=it.next(); if(e==s) return index; } return -1; } - /** + public Slot makeSlot(String name){ + return new Slot(name); + } + /** * this version will get or create a slot by given name. * @param name * @return */ - public Slot getSlot(String name){ - int index=findSlot(name); + public Slot getSlot(String name,boolean make){ + int index=indexOf(name); if(index<0){ - return new Slot(name); + return make?makeSlot(name):null; }else{ return getSlot(index); } @@ -116,11 +144,7 @@ public class Hdr { keys.set(index,s); return this; } - public Slot[] slots(Slot... slots){ - if(slots!=null && slots.length>0){ - keys.clear(); - for(int i=0;i 0) { diff --git a/src/main/java/com/reliancy/rec/Obj.java b/src/main/java/com/reliancy/rec/Obj.java index e345ade..bf446c7 100644 --- a/src/main/java/com/reliancy/rec/Obj.java +++ b/src/main/java/com/reliancy/rec/Obj.java @@ -118,7 +118,7 @@ public class Obj implements Rec{ if(s==null) throw new IllegalArgumentException("invalid key provided"); if(isArray()) throw new IllegalStateException("array not mappable with:"+s.getName()); int index=s.getPosition(); // try slot position - if(index<0) index=meta.findSlot(s.getName());// fall back to search if slot not set + if(index<0) index=meta.indexOf(s.getName());// fall back to search if slot not set if(index<0){ values.add(val); meta.addSlot(s); @@ -138,14 +138,14 @@ public class Obj implements Rec{ if(s==null) throw new IllegalArgumentException("invalid key provided"); //if(keys==null) throw new IllegalStateException("array not mappable with:"+s.getName()); int index=s.getPosition(); // try slot position - if(index<0 && !isArray()) index=meta.findSlot(s.getName());// fall back to search if slot not set + if(index<0 && !isArray()) index=meta.indexOf(s.getName());// fall back to search if slot not set return index<0?def:values.get(index); } @Override public Rec remove(Slot s) { int index=s.getPosition(); // try slot position - if(index<0 && !isArray()) index=meta.findSlot(s.getName());// fall back to search if slot not set + if(index<0 && !isArray()) index=meta.indexOf(s.getName());// fall back to search if slot not set if(index>=0) remove(index); return this; } diff --git a/src/main/java/com/reliancy/rec/Rec.java b/src/main/java/com/reliancy/rec/Rec.java index fd5c6ec..976babb 100644 --- a/src/main/java/com/reliancy/rec/Rec.java +++ b/src/main/java/com/reliancy/rec/Rec.java @@ -11,7 +11,7 @@ public interface Rec extends Vec{ public Rec remove(Slot s); public default Slot getSlot(String name){ Hdr m=meta(); - return m!=null?m.getSlot(name):null; + return m!=null?m.getSlot(name,true):null; } public default Slot getSlot(int pos){ Hdr m=meta(); diff --git a/src/main/java/com/reliancy/rec/Slot.java b/src/main/java/com/reliancy/rec/Slot.java index e0d9b30..1962a97 100644 --- a/src/main/java/com/reliancy/rec/Slot.java +++ b/src/main/java/com/reliancy/rec/Slot.java @@ -11,7 +11,7 @@ public class Slot extends Hdr { Object getInitalValue(Slot s,Rec rec); } public static final Initializer DEFAULT_INITIALIZER=new Initializer(){ - public Object getInitalValue(Slot s,Rec rec) {return s.getDefaultValue();} + public Object getInitalValue(Slot s,Rec rec) {return s.getInitValue();} }; int position; Object defaultValue; @@ -25,23 +25,29 @@ public class Slot extends Hdr { this.position=-1; this.initValue=DEFAULT_INITIALIZER; } + public boolean equals(String str){ + return name.equalsIgnoreCase(str); + } public int getPosition() { return position; } - public void setPosition(int position) { + public Slot setPosition(int position) { this.position = position; + return this; } - public Object getDefaultValue() { + public Object getInitValue() { return defaultValue; } - public void setDefaultValue(Object defaultValue) { + public Slot setInitValue(Object defaultValue) { this.defaultValue = defaultValue; + return this; } - public Initializer getInitValue() { + public Initializer getInitVia() { return initValue; } - public void setInitValue(Initializer initValue) { + public Slot setInitVia(Initializer initValue) { this.initValue = initValue; + return this; } public int toString(Object val, StringBuilder buf) { int length0=buf.length(); diff --git a/src/main/java/com/reliancy/util/CloseableIterator.java b/src/main/java/com/reliancy/util/CloseableIterator.java deleted file mode 100644 index 36d7cca..0000000 --- a/src/main/java/com/reliancy/util/CloseableIterator.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.reliancy.util; - -import java.io.Closeable; -import java.util.Iterator; - -public interface CloseableIterator extends Iterator, Closeable { -} \ No newline at end of file diff --git a/src/main/java/com/reliancy/util/Handy.java b/src/main/java/com/reliancy/util/Handy.java index 81d5ddb..d9f9ac1 100644 --- a/src/main/java/com/reliancy/util/Handy.java +++ b/src/main/java/com/reliancy/util/Handy.java @@ -45,7 +45,7 @@ public final class Handy { if(clazz.isAssignableFrom(val.getClass())) return clazz; // we are assignable if(val instanceof String){ String value=(String) val; - if(value.isBlank() || value.equals("''") || value.equals("\"\"")) return null; + if(value.isEmpty() || value.equals("''") || value.equals("\"\"")) return null; if( Boolean.class==( clazz ) || boolean.class==( clazz ) ) return Boolean.parseBoolean( value ); if( Byte.class==( clazz ) || byte.class==( clazz ) ) return Byte.parseByte( value ); if( Short.class==( clazz ) || short.class==( clazz ) ) return Short.parseShort( value ); @@ -454,5 +454,31 @@ public final class Handy { all.toArray(ret); return ret; } - + public static String toString(Object...args){ + StringBuilder buf=new StringBuilder(); + if(args.length>1){ + buf.append("["); + for(int i=0;i it=((Iterable)arg).iterator(); + buf.append("["); + while(it.hasNext()) buf.append(buf.length()>1?",":"").append(it.next()); + buf.append("]"); + }else + if(arg instanceof java.util.Map){ + java.util.Map marg=(Map) arg; + buf.append("{"); + for(java.util.Map.Entrye:marg.entrySet()){ + buf.append(e.getKey().toString()).append(":").append(toString(e.getValue())); + } + buf.append("}"); + }else{ + buf.append(String.valueOf(arg)); + } + } + return buf.toString(); + } } diff --git a/src/main/java/com/reliancy/util/Template.java b/src/main/java/com/reliancy/util/Template.java index 46276de..57ed785 100644 --- a/src/main/java/com/reliancy/util/Template.java +++ b/src/main/java/com/reliancy/util/Template.java @@ -1,20 +1,28 @@ package com.reliancy.util; +import java.io.FileNotFoundException; import java.io.IOException; import java.net.URL; -import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; +import com.github.jknack.handlebars.Handlebars; +import com.github.jknack.handlebars.io.AbstractTemplateLoader; +import com.github.jknack.handlebars.io.TemplateSource; +import com.github.jknack.handlebars.io.URLTemplateSource; + +/* import com.hubspot.jinjava.Jinjava; import com.hubspot.jinjava.interpret.JinjavaInterpreter; import com.hubspot.jinjava.loader.ResourceLocator; +*/ /** * We will manage template rendering thru this class. */ public class Template { + /* static Jinjava jinjava; static{ jinjava = new Jinjava(); @@ -32,6 +40,27 @@ public class Template { } } } + */ + public static class HBLoader extends AbstractTemplateLoader{ + public HBLoader(){ + this.setPrefix("/templates/"); + } + @Override + public TemplateSource sourceAt(String location) throws IOException { + String fullpath=this.resolve(location); + URL loc=Resources.findFirst(null,fullpath,Template.search_path); + System.out.println(location+":"+loc+":"+fullpath); + if (loc == null) { + Logger.getLogger(Template.class.getSimpleName()).warning("Missing template"+fullpath); + throw new FileNotFoundException(location); + } + return new URLTemplateSource(location,loc); + } + + + } + static Handlebars handlebars = new Handlebars(new HBLoader()); + static Object[] search_path; static HashMap cache=new HashMap<>(); /** renders a template to string, possibly locates it first. @@ -56,7 +85,6 @@ public class Template { Template ret=cache.get(path); if(ret!=null) return ret; URL loc=Resources.findFirst(null, path, (sp!=null && sp.length>0?sp:search_path)); - System.out.println("TLOCL:"+loc); if(loc==null) return null; ret=new Template(loc); cache.put(path,ret); @@ -66,7 +94,7 @@ public class Template { if(sp!=null && sp.length>0) search_path=sp; return search_path; } - + com.github.jknack.handlebars.Template recipe; final URL location; String source; public Template(URL location){ @@ -88,7 +116,11 @@ public class Template { } public CharSequence render(Map context) throws IOException{ if(source==null) load(); - String ret = jinjava.render(source, context); + //String ret = jinjava.render(source, context); + if(recipe==null){ + recipe=handlebars.compileInline(source); + } + String ret=recipe.apply(context); return ret; } } diff --git a/src/main/resources/templates/login.hbs b/src/main/resources/templates/login.hbs new file mode 100644 index 0000000..784dec7 --- /dev/null +++ b/src/main/resources/templates/login.hbs @@ -0,0 +1,4 @@ +{{#partial "content"}} +Calling base +{{/partial}} +{{> base}} diff --git a/src/test/java/com/reliancy/dbo/TerminalTest.java b/src/test/java/com/reliancy/dbo/TerminalTest.java index 0365dfa..ca4e5ea 100644 --- a/src/test/java/com/reliancy/dbo/TerminalTest.java +++ b/src/test/java/com/reliancy/dbo/TerminalTest.java @@ -4,25 +4,67 @@ import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.sql.Timestamp; +import java.util.Date; +import com.reliancy.rec.JSON; + +import org.junit.BeforeClass; import org.junit.Test; public class TerminalTest { + @Entity.Info( + name="dbo.Maps" + ) public static class Maps extends DBO{ - public static Field map_id=new Field("Map_id",Integer.class); - public static Field map_name=new Field("Map_name",String.class); + public static Field map_id=Field.Int("Map_id").setPk(true); + public static Field map_name=Field.Str("Map_name"); + public static Field created=Field.DateTime("Created"); + public static Field active=Field.Bool("Active"); static{ - Entity.publish(Maps.class); + //Entity.publish(Maps.class); } } + @Entity.Info( + name="public.securable" + ) + public static class Securable extends DBO{ + public static Field id=Field.Int("id").setPk(true).setAutoIncrement(true); + public static Field kind=Field.Str("kind"); + public static Field name=Field.Str("name"); + public static Field display_name=Field.Str("display_name"); + public static Field created=Field.DateTime("created_on"); + public static Field is_essential=Field.Bool("is_essential"); + static{ + //Entity.publish(Maps.class); + } + } + + @Entity.Info( + name="public.product" + ) + public static class Product extends Securable{ + public static Field valid_since=Field.DateTime("valid_since"); + public static Field valid_until=Field.DateTime("valid_until"); + public static Field short_info=Field.Str("short_info"); + + } + + static SQLTerminal t; + + @BeforeClass + public static void beforeAllTestMethods() { + System.out.println("Invoked once before all test methods"); + String url="jdbc:postgresql://postgres:Ramudin99@bigbang:5432/Test"; + t=new SQLTerminal(url); + } + /** - * Plain CRUD + * jdbc connectivity * @throws IOException * @throws SQLException */ @Test public void connection() throws IOException, SQLException{ - String url="jdbc:postgresql://postgres:Ramudin99@bigbang:5432/Test"; - SQLTerminal t=new SQLTerminal(url); try(Connection c=t.getConnection()){ System.out.println("Connection:"+c); try (Statement stmt = c.createStatement()) { @@ -38,5 +80,38 @@ public class TerminalTest { } } } + @Test + public void simpleCRUD() throws IOException, SQLException{ + System.out.println("SimpleCRUD"); + try(Action act=t.begin().load(Maps.class).execute()){ + for(DBO o:act){ + System.out.println("DBO:"+o); + } + } + Entity.retract(Maps.class); + } + @Test + public void complexCRUD() throws IOException, SQLException{ + System.out.println("ComplexCRUD"); + try(Action act=t.begin().load(Product.class).execute()){ + for(DBO o:act){ + System.out.println("DBO:"+o); + } + } + Product p=new Product(); + p.setStatus(DBO.Status.USED); + Product.kind.set(p,Product.class.getSimpleName()); + Product.name.set(p,"myproduct"); + Product.created.set(p,new Date()); + Product.short_info.set(p,"a sweet melody"); + Product.display_name.set(p,"first entry"); + System.out.println("P0:"+JSON.toString(p)); + t.save(p); + System.out.println("P1:"+JSON.toString(p)); + Product pp=t.load(Product.class, 35); + System.out.println("Returning:"+pp); + //t.delete(pp); + Entity.retract(Maps.class); + } } diff --git a/var/base.j2 b/var/base.j2 deleted file mode 100644 index b93bd5c..0000000 --- a/var/base.j2 +++ /dev/null @@ -1,27 +0,0 @@ - - - - Flask Template Example - - - - - -
-

This is part of my base template

-
- {% block content %}{% endblock %} -
-

This is part of my base template

-
- - - - - \ No newline at end of file