fixed shutdown sequence, added better modularity

This commit is contained in:
Amer Agovic
2024-01-15 10:45:40 -06:00
parent 32cf0ecd66
commit 01d49e1ee9
40 changed files with 1464 additions and 386 deletions
+6 -6
View File
@@ -1,11 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<classpath> <classpath>
<classpathentry kind="src" output="target/classes/java/main" path="src/main/java">
<attributes>
<attribute name="gradle_scope" value="main"/>
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/classes/java/test" path="src/test/java"> <classpathentry kind="src" output="target/classes/java/test" path="src/test/java">
<attributes> <attributes>
<attribute name="gradle_scope" value="test"/> <attribute name="gradle_scope" value="test"/>
@@ -19,6 +13,12 @@
<attribute name="gradle_used_by_scope" value="main,test"/> <attribute name="gradle_used_by_scope" value="main,test"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="src" output="target/classes/java/main" path="src/main/java">
<attributes>
<attribute name="gradle_scope" value="main"/>
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/> <classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="target/bin"/> <classpathentry kind="output" path="target/bin"/>
+6 -6
View File
@@ -1,13 +1,13 @@
arguments=--init-script C\:\\Users\\agov0001\\AppData\\Roaming\\Code\\User\\globalStorage\\redhat.java\\1.22.1\\config_win\\org.eclipse.osgi\\57\\0\\.cp\\gradle\\init\\init.gradle --init-script C\:\\Users\\agov0001\\AppData\\Roaming\\Code\\User\\globalStorage\\redhat.java\\1.22.1\\config_win\\org.eclipse.osgi\\57\\0\\.cp\\gradle\\protobuf\\init.gradle arguments=
auto.sync=false auto.sync=false
build.scans.enabled=false build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(LOCAL_INSTALLATION(C\:\\ProgramData\\chocolatey\\lib\\gradle\\tools\\gradle-7.5.1)) connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir= connection.project.dir=
eclipse.preferences.version=1 eclipse.preferences.version=1
gradle.user.home= gradle.user.home=
java.home=C\:/Program Files/Microsoft/jdk-11.0.12.7-hotspot java.home=
jvm.arguments= jvm.arguments=
offline.mode=false offline.mode=false
override.workspace.settings=true override.workspace.settings=false
show.console.view=true show.console.view=false
show.executions.view=true show.executions.view=false
+1 -1
View File
@@ -10,7 +10,7 @@ org.eclipse.jdt.core.circularClasspath=warning
org.eclipse.jdt.core.classpath.exclusionPatterns=enabled org.eclipse.jdt.core.classpath.exclusionPatterns=enabled
org.eclipse.jdt.core.classpath.mainOnlyProjectHasTestOnlyDependency=error org.eclipse.jdt.core.classpath.mainOnlyProjectHasTestOnlyDependency=error
org.eclipse.jdt.core.classpath.multipleOutputLocations=enabled org.eclipse.jdt.core.classpath.multipleOutputLocations=enabled
org.eclipse.jdt.core.classpath.outputOverlappingAnotherSource=error org.eclipse.jdt.core.classpath.outputOverlappingAnotherSource=ignore
org.eclipse.jdt.core.codeComplete.argumentPrefixes= org.eclipse.jdt.core.codeComplete.argumentPrefixes=
org.eclipse.jdt.core.codeComplete.argumentSuffixes= org.eclipse.jdt.core.codeComplete.argumentSuffixes=
org.eclipse.jdt.core.codeComplete.camelCaseMatch=enabled org.eclipse.jdt.core.codeComplete.camelCaseMatch=enabled
+41 -15
View File
@@ -4,26 +4,50 @@ Unix - ~/.m2
Windows - C:\Users\<username>\.m2 Windows - C:\Users\<username>\.m2
For example - /Users/alex/.m2/repository/<library_path>/<version>/<name>.<extension> For example - /Users/alex/.m2/repository/<library_path>/<version>/<name>.<extension>
*/ */
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
apply plugin: 'application' apply plugin: 'application'
apply plugin: 'eclipse' apply plugin: 'eclipse'
apply from: 'extra.gradle' apply from: 'extra.gradle'
group='com.reliancy'
mainClassName = group+'.'+name+'.JettyApp'
version = '0.2-SNAPSHOT'
sourceCompatibility = 1.8
targetCompatibility = 1.8
project.buildDir = 'target' project.buildDir = 'target'
group='com.reliancy'
version = '0.3-SNAPSHOT'
application{
mainClass=(group+'.'+name+'.JettyApp')
}
java{
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
// print some info for orientation // print some info for orientation
println("group:"+group); //println("group:"+group);
println("name:"+name); //println("name:"+name);
println("version:"+version); //println("version:"+version);
println("entry:"+mainClassName); //println("entry:"+mainClassName);
sourceSets {
main {
resources {
srcDirs "src/main/resources", "src/main/web"
}
}
}
processResources {
from (sourceSets.main.java.srcDirs) {
include '**/*.htm'
include '**/*.html'
include '**/*.properties'
include '**/*.ini'
include '**/*.json'
include '**/*.js'
include '**/*.css'
include '**/*.txt'
include '**/*.xml'
include '**/*.png'
}
}
repositories { repositories {
//mavenLocal() //mavenLocal()
//mavenCentral() //mavenCentral()
@@ -75,7 +99,8 @@ javadoc {
} }
dependencies { dependencies {
implementation "org.eclipse.jetty:jetty-server:11.0.1" implementation "org.eclipse.jetty:jetty-server:11.0.1"
implementation "org.slf4j:slf4j-simple:2.0.0-alpha0" implementation "org.slf4j:slf4j-jdk14:2.0.10"
//implementation "org.slf4j:slf4j-simple:2.0.10"
//implementation 'com.hubspot.jinjava:jinjava:2.5.10' //implementation 'com.hubspot.jinjava:jinjava:2.5.10'
implementation 'com.github.jknack:handlebars:4.3.0' implementation 'com.github.jknack:handlebars:4.3.0'
implementation 'com.h2database:h2:2.1.214' implementation 'com.h2database:h2:2.1.214'
@@ -92,9 +117,10 @@ test {
// standard out or error is shown // standard out or error is shown
// in Gradle output. // in Gradle output.
outputs.upToDateWhen {false} outputs.upToDateWhen {false}
showStandardStreams = true //showStandardStreams = true
exceptionFormat = 'full' exceptionFormat = 'full'
// Or we use events method: // Or we use events method:
events "passed", "skipped", "failed", "standardOut", "standardError"
// events 'standard_out', 'standard_error' // events 'standard_out', 'standard_error'
// Or set property events: // Or set property events:
@@ -110,7 +136,7 @@ jar {
archiveBaseName = project.name archiveBaseName = project.name
archiveVersion = project.version archiveVersion = project.version
manifest { manifest {
attributes "Main-Class": mainClassName attributes "Main-Class": application.mainClass
attributes "Class-Path": configurations.runtimeClasspath.collect { it.getName() }.join(' ') attributes "Class-Path": configurations.runtimeClasspath.collect { it.getName() }.join(' ')
} }
} }
@@ -121,11 +147,11 @@ task copyToLib(type: Copy) {
build.finalizedBy(copyToLib) build.finalizedBy(copyToLib)
eclipse{ eclipse{
classpath { classpath {
defaultOutputDir = file("target/bin") ///default defaultOutputDir = file("${relativePath(buildDir)}/bin") ///default
file.whenMerged { cp -> file.whenMerged { cp ->
cp.entries.forEach { cpe -> cp.entries.forEach { cpe ->
if (cpe.kind == 'src' && cpe.hasProperty('output')) { if (cpe.kind == 'src' && cpe.hasProperty('output')) {
cpe.output = cpe.output.replace('bin/', "target/classes/java/") cpe.output = cpe.output.replace('bin/', "${relativePath(buildDir)}/classes/java/")
} }
} }
} }
+5 -5
View File
@@ -12,11 +12,11 @@ task dotenv{
} }
task packageJavadoc(type: Jar, dependsOn: 'javadoc') { task packageJavadoc(type: Jar, dependsOn: 'javadoc') {
from javadoc from javadoc
classifier = 'javadoc' archiveClassifier = 'javadoc'
} }
task packageSources(type: Jar, dependsOn: 'classes') { task packageSources(type: Jar, dependsOn: 'classes') {
from sourceSets.main.allSource from sourceSets.main.allSource
classifier = 'sources' archiveClassifier = 'sources'
} }
task fat_jar(type: Jar) { task fat_jar(type: Jar) {
archiveBaseName = 'fat-'+project.name archiveBaseName = 'fat-'+project.name
@@ -92,7 +92,7 @@ class Server implements Runnable{
info("stopping server"); info("stopping server");
if(driver!=null){ if(driver!=null){
driver.interrupt(); driver.interrupt();
driver.join(); //driver.join();
} }
for(Thread th:Thread.getAllStackTraces().keySet()){ for(Thread th:Thread.getAllStackTraces().keySet()){
if(th.getName().equalsIgnoreCase("executor")){ if(th.getName().equalsIgnoreCase("executor")){
@@ -116,8 +116,8 @@ task runServer{
doLast { doLast {
Server.main().start({ Server.main().start({
project.javaexec { project.javaexec {
classpath = project.sourceSets.main.runtimeClasspath classpath = sourceSets.main.runtimeClasspath
main = mainClassName main = application.mainClass.get()
} }
}); });
} }
+1 -1
View File
@@ -17,7 +17,7 @@ import java.util.Iterator;
* At its core are action traits which are classes that define either loading,saving or deleting. * 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. * 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 * 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. * once iterated over and saved we are done.
*/ */
public class Action implements Iterable<DBO>,SiphonIterator<DBO>{ public class Action implements Iterable<DBO>,SiphonIterator<DBO>{
public static class Trait{ public static class Trait{
@@ -22,6 +22,10 @@ import com.reliancy.util.Path;
import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
/** SQL particular implementation of a terminal.
* It will use a connection pool under it to take care of connection re-use.
*
*/
public class SQLTerminal implements Terminal{ public class SQLTerminal implements Terminal{
HikariConfig config = new HikariConfig(); HikariConfig config = new HikariConfig();
HikariDataSource ds; HikariDataSource ds;
@@ -48,7 +52,7 @@ public class SQLTerminal implements Terminal{
} }
@Override @Override
public Action execute(Action q) throws IOException{ public Action execute(Action q) throws IOException{
System.out.println("Executing..."+q.getTrait()); // System.out.println("Executing..."+q.getTrait());
Action.Trait tr=q.getTrait(); Action.Trait tr=q.getTrait();
if(tr instanceof Action.Load){ if(tr instanceof Action.Load){
Entity ent=q.getEntity(); Entity ent=q.getEntity();
@@ -60,14 +64,14 @@ public class SQLTerminal implements Terminal{
reader.close(); reader.close();
throw new IOException(e); throw new IOException(e);
} }
System.out.println("Executing...Done"); //System.out.println("Executing...Done");
return q; return q;
}else if(tr instanceof Action.Save){ }else if(tr instanceof Action.Save){
Entity ent=q.getEntity(); Entity ent=q.getEntity();
try(SQLWriter writer=new SQLWriter(ent,this)) { try(SQLWriter writer=new SQLWriter(ent,this)) {
writer.open(); writer.open();
writer.flush(q.getItems()); writer.flush(q.getItems());
System.out.println("Executing...Done"); //System.out.println("Executing...Done");
return q; return q;
}catch(SQLException e){ }catch(SQLException e){
throw new IOException(e); throw new IOException(e);
@@ -77,7 +81,7 @@ public class SQLTerminal implements Terminal{
try(SQLCleaner cleaner=new SQLCleaner(ent,this)) { try(SQLCleaner cleaner=new SQLCleaner(ent,this)) {
cleaner.open(); cleaner.open();
cleaner.flush(q.getItems()); cleaner.flush(q.getItems());
System.out.println("Executing...Done"); //System.out.println("Executing...Done");
return q; return q;
}catch(SQLException e){ }catch(SQLException e){
throw new IOException(e); throw new IOException(e);
+1 -1
View File
@@ -14,7 +14,7 @@ import java.io.IOException;
* control will be implemented via meta terminal which will return specialized terminals for each entity and running actions on it * control will be implemented via meta terminal which will return specialized terminals for each entity and running actions on it
* will modify the entity structure. * will modify the entity structure.
* *
* the core of the temrminal will be the Action object. The others will just be wrappers for since item actions. * the core of the temrminal will be the Action object. The others will just be wrappers for item actions.
* the action will be a read or write query with session management. * the action will be a read or write query with session management.
*/ */
public interface Terminal { public interface Terminal {
+84 -21
View File
@@ -8,47 +8,81 @@ You may not use this file except in compliance with the License.
package com.reliancy.jabba; package com.reliancy.jabba;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.logging.Logger;
import com.reliancy.dbo.Terminal;
import com.reliancy.jabba.sec.SecurityPolicy; import com.reliancy.jabba.sec.SecurityPolicy;
import com.reliancy.util.CodeException; import com.reliancy.util.CodeException;
import com.reliancy.util.ResultCode; import com.reliancy.util.ResultCode;
/** Base Application class from where specific launchers derive. /** Base Application class from where specific launchers derive.
* Derived classes will usually bring in jetty or tomcat or some other launch ability. * Derived classes will usually bring in jetty or tomcat or some other launch ability.
* At the level of App we manage pure app or infrastructure concepts.
* Examples of such concepts are:
* - processor chain
* - main / router processor
* - security policy
* - storage which pools resources among instances
* - app wide logger and config
* It does not include:
* - per user config
* - per user work directory
*
* Storage is an abstract, possibly external data store. It could be file based or not.
* On the other hand work path is usually local disk path specific to a machine, still
* it can be overriden to return www resource paths for certain items. That is why we treat
* it as a string and not a File or URL.
*/ */
public abstract class App extends Processor{ public abstract class App extends Processor{
public static int ERR_NOCONFIG=ResultCode.defineFailure(0x01,App.class,"config missing. provide at least empty one."); public static int ERR_NOCONFIG=ResultCode.defineFailure(0x01,App.class,"config missing. provide at least empty one.");
public static int ERR_NOTCLOSED=ResultCode.defineFailure(0x02,App.class,"unbalanced call. resource called twice:${resource}"); public static int ERR_NOTCLOSED=ResultCode.defineFailure(0x02,App.class,"unbalanced call. resource called twice:${resource}");
protected Processor first=null; protected Processor first=null;
protected Processor last=null; protected Processor last=null;
protected RoutedEndPoint router=null; protected Router router=null;
protected SecurityPolicy policy=null; protected SecurityPolicy policy=null;
protected Terminal storage=null;
public App(String id) { public App(String id) {
super(id); super(id);
} }
/** does nothing. */
public void before(Request request,Response response) throws IOException{ public void before(Request request,Response response) throws IOException{
} }
/** does nothing. */
public void after(Request request,Response response) throws IOException{ public void after(Request request,Response response) throws IOException{
} }
/** app serves by processing first-last chain then router.
* always conditional on status being null otherwise it skips.
*/
public void serve(Request req,Response resp) throws IOException{ public void serve(Request req,Response resp) throws IOException{
if(first!=null) first.process(req, resp); if(first!=null && resp.getStatus()==null) first.process(req, resp);
if(router!=null) router.process(req,resp); if(router!=null && resp.getStatus()==null) router.process(req,resp);
} }
public <T extends Processor> T addProcessor(T m){ /** add one or a chain of processors. */
public <T extends Processor> T addMiddleWare(T m){
if(m==null) return null;
if(first==null){ if(first==null){
last=first=m; last=first=m;
m.setParent(m);
}else{ }else{
last.next=m; last.next=m;
} }
while(last.next!=null) last=last.next; while(last.next!=null){
last=last.next;
last.setParent(m);
}
return m; return m;
} }
public void removeProcessor(Processor m){ public void removeMiddleWare(Processor m){
if(m==null) return;
if(first==m){ if(first==m){
if(first==last) last=null; if(first==last){
first=first.next; first=last=null;
}else{
first=first.next;
}
while(last!=null && last.next!=null) last=last.next; while(last!=null && last.next!=null) last=last.next;
}else{ }else{
for(Processor prev=first;prev!=null;prev=prev.next){ for(Processor prev=first;prev!=null;prev=prev.next){
@@ -60,6 +94,7 @@ public abstract class App extends Processor{
} }
} }
m.next=null; m.next=null;
m.setParent(null);
} }
public Processor getProcessor(String id){ public Processor getProcessor(String id){
for(Processor c=first;c!=null;c=c.next){ for(Processor c=first;c!=null;c=c.next){
@@ -68,11 +103,26 @@ public abstract class App extends Processor{
return null; return null;
} }
public RoutedEndPoint getRouter() { public Router getRouter() {
return router; return router;
} }
public void setRouter(RoutedEndPoint router) { public void setRouter(Router router) {
if(this.router==router) return;
if(this.router!=null) this.router.setParent(null);
this.router = router; this.router = router;
router.setParent(this);
}
public String getWorkPath(String rel_path){
Config cnf=getConfig();
String work_dir=Config.APP_WORKDIR.get(cnf);
if(!work_dir.endsWith("/")) work_dir+="/";
if(rel_path==null) rel_path="";
if(rel_path.startsWith("/")) rel_path=rel_path.substring(1);
if(rel_path==".") rel_path="";
rel_path=rel_path.replace("\\","/");
rel_path=rel_path.replace("/./","/");
String ret=work_dir+rel_path;
return ret;
} }
public void run(Config conf) throws Exception { public void run(Config conf) throws Exception {
try{ try{
@@ -88,34 +138,41 @@ public abstract class App extends Processor{
if(conf==null) throw new CodeException(ERR_NOCONFIG); if(conf==null) throw new CodeException(ERR_NOCONFIG);
config=conf; config=conf;
for(Processor p=first;p!=null;p=p.getNext()){ for(Processor p=first;p!=null;p=p.getNext()){
p.begin(config); p.begin();
} }
if(router!=null) router.begin(config); if(router!=null) router.begin(config);
} }
@Override @Override
public void end() throws Exception{ public void end() throws Exception{
if(router!=null) router.end(); try{
for(Processor p=first;p!=null;p=p.getNext()){ if(router!=null) router.end();
p.end(); for(Processor p=first;p!=null;p=p.getNext()){
p.end();
}
log().info("stopped:"+getId());
super.end(); // detaches from config
}finally{
// we notify all of end (especially cleaner thread)
synchronized(this){
this.notifyAll();
}
} }
super.end();
log().info("stopping app:"+getId());
} }
public AppSessionFilter addAppSession(){ public AppSessionFilter addAppSession(){
return addProcessor(new AppSessionFilter(this)); return addMiddleWare(new AppSessionFilter(this));
} }
public AppSessionFilter addAppSession(AppSession.Factory f){ public AppSessionFilter addAppSession(AppSession.Factory f){
return addProcessor(new AppSessionFilter(this,f)); return addMiddleWare(new AppSessionFilter(this,f));
} }
public SecurityPolicy setSecurityPolicy(SecurityPolicy secpol){ public SecurityPolicy setSecurityPolicy(SecurityPolicy secpol){
if(secpol==policy) return secpol; if(secpol==policy) return secpol;
if(policy!=null){ if(policy!=null){
MethodDecorator.retract(policy); MethodDecorator.retract(policy);
removeProcessor(policy); removeMiddleWare(policy);
} }
policy=secpol; policy=secpol;
if(policy!=null){ if(policy!=null){
addProcessor(policy); addMiddleWare(policy);
MethodDecorator.publish(policy); // register security policy as decorator factory MethodDecorator.publish(policy); // register security policy as decorator factory
} }
return secpol; return secpol;
@@ -123,4 +180,10 @@ public abstract class App extends Processor{
public SecurityPolicy getSecurityPolicy(){ public SecurityPolicy getSecurityPolicy(){
return policy; return policy;
} }
public void setStorage(Terminal db){
storage=db;
}
public Terminal getStorage(){
return storage;
}
} }
@@ -0,0 +1,9 @@
package com.reliancy.jabba;
/** Special controller interface which is implemented by modules.
* Modules are classes that publish or retract middleware or endpoints into application.
* They allow us to compose an application of disparate apis.
*/
public interface AppModule {
void publish(App app);
default void retract(App app){};
}
@@ -11,6 +11,9 @@ import java.util.HashMap;
import com.reliancy.jabba.sec.SecurityActor; import com.reliancy.jabba.sec.SecurityActor;
import com.reliancy.jabba.ui.Feedback; import com.reliancy.jabba.ui.Feedback;
/** AppSession is recovered early on and holds per-user app instance data.
* Unless it is owned by user db terminals should not be held here.
*/
public class AppSession implements Session{ public class AppSession implements Session{
public static interface Factory{ public static interface Factory{
@@ -0,0 +1,191 @@
package com.reliancy.jabba;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import com.reliancy.util.Handy;
import com.reliancy.util.Log;
/**
* ArgsConfig takes in command line arguments and parses them on load.
* If also deals with environmental variables which are returned if not present.
* Note that in java we have System properties which are platform independent
* then we have environment variables which are not unless fed by us directly.
*/
public class ArgsConfig extends Config.Base{
final String[] args;
final ArrayList<Property<?>> schema=new ArrayList<>();
String id;
public ArgsConfig(String... args){
this.args=args;
this.id="";
}
@Override
public Config load() throws IOException {
if(props.isEmpty()==false) return this; // not gona load again if loaded
final Map<String, List<String>> params = new HashMap<>();
List<String> values=null;
ArrayList<String> positional=new ArrayList<>();
String app_invoked="";
for (int i = 0; i < args.length; i++) {
final String a = args[i];
if (a.charAt(0) == '-') {
if(!positional.isEmpty()){
// positional filled - we are entering new level
throw new RuntimeException("multi level arguments not supported");
}
String key=a.substring(1);
boolean brief=!key.startsWith("-");
if(!brief) key=key.substring(1);
if (key.length() < 1) {
throw new IllegalArgumentException("bad argument -");
}
values=new ArrayList<>();
params.put(key,values);
if(key.contains("=")){
// we got value after equals
String[] toks =key.split("=",1);
key=toks[0];
String val=toks[1];
val=Handy.unwrap(val,"\"","\"");
val=Handy.unwrap(val,"'","'");
values.add(val);
}
}else if (values != null) {
//TODO: if property had a max count we could delay
// this way multiple values require repeated --key val
String val=a;
val=Handy.unwrap(val,"\"","\"");
val=Handy.unwrap(val,"'","'");
values.add(a);
values=null;
}else {
// these are positional arguments
if(i==0) app_invoked=a; else positional.add(a);
}
}
// now process parsed
id=app_invoked;
this.setProperty(Config.APP_INVOKED,app_invoked);
int lastDelim=Math.max(app_invoked.lastIndexOf('/'),app_invoked.lastIndexOf('\\'));
this.setProperty(Config.APP_NAME,app_invoked.substring(lastDelim+1));
this.setProperty(Config.APP_ARGS,positional);
//System.out.println("App invoked:"+app_invoked);
//System.out.println("params:"+params);
//System.out.println("pos:"+positional);
for(String pkey:params.keySet()){
values=params.get(pkey);
if(values.isEmpty()){
// we got a boolean
Property<Boolean> prop=new Property<>(pkey,Boolean.class);
setProperty(prop,true);
}else if(values.size()==1){
// we got single value
String val=values.get(0);
if("true".equalsIgnoreCase(val) || "false".equalsIgnoreCase(val)){
Property<Boolean> prop=new Property<>(pkey,Boolean.class);
Boolean val2=prop.adaptValue(val);
setProperty(prop,val2);
}else if(Handy.isNumeric(val)){
Property<Float> prop=new Property<>(pkey,Float.class);
Float val2=prop.adaptValue(val);
setProperty(prop,val2);
}else{
Property<String> prop=new Property<>(pkey,String.class);
setProperty(prop,val);
}
}else{
// we got a list
Property<List> prop=new Property<>(pkey,List.class);
setProperty(prop,values);
}
}
// finally post process such as choosing APP_WORKDIR
String cwd=null;
String[] cwds=new String[]{APP_WORKDIR.get(this),"./var","./data","../var","../data"};
for(String c:cwds){
if(c==null) continue;
File f=new File(c);
if(f.exists() && f.isDirectory()){
cwd=c;break;
}
}
if(cwd!=null){ // we got working dir
cwd=cwd.replace("\\", "/").replace("/./","/");
if(!cwd.endsWith("/")) cwd+="/";
APP_WORKDIR.set(this, cwd);
}
// also logging level and format
Logger root=Log.setup();
Log.setLevel(root,LOG_LEVEL.get(this));
return this;
}
@Override
public Config save() {
return this;
}
@Override
public String getId() {
return id;
}
@Override
public <T> boolean hasProperty(Property<T> key){
String BADVAL="___BADVAL___";
if(props.containsKey(key)){
return true;
}else if(System.getProperty(key.getName(),BADVAL)!=BADVAL){
return true;
}else if(System.getenv().containsKey(key.getName())){
return true;
}else{
return false;
}
}
@Override
public <T> T getProperty(Property<T> key, T def) {
String BADVAL="___BADVAL___";
if(props.containsKey(key)){
return key.getTyp().cast(props.get(key));
}
String ret=System.getProperty(key.getName(),BADVAL);
if(ret!=BADVAL){
Object val=Handy.normalize(key.getTyp(),ret);
return key.getTyp().cast(val);
}else if(System.getenv().containsKey(key.getName())){
Object val=System.getenv(key.getName());
val=Handy.normalize(key.getTyp(),val);
return key.getTyp().cast(val);
}else{
return def;
}
}
@Override
public <T> Config setProperty(Property<T> key, T val) {
props.put(key,val);
return this;
}
@Override
public <T> T delProperty(Property<T> key) {
Object val=props.remove(key);
return key.getTyp().cast(val);
}
@Override
public Iterator<Property<?>> iterator() {
ArrayList<Property<?>> keys=new ArrayList<>(props.keySet());
return keys.iterator();
}
@Override
public Config setSchema(Property<?> ...p){
this.schema.clear();
for(Property<?> pp:p) schema.add(pp);
return this;
}
}
+101 -6
View File
@@ -6,35 +6,130 @@ You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en
You may not use this file except in compliance with the License. You may not use this file except in compliance with the License.
*/ */
package com.reliancy.jabba; package com.reliancy.jabba;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.slf4j.Logger; import org.slf4j.Logger;
public interface Config { import com.reliancy.util.Handy;
public interface Config extends Iterable<Config.Property<?>>{
public static class Property<V> { public static class Property<V> {
final String name; final String name;
final Class<V> typ; final Class<V> typ;
V initial;
boolean required;
boolean writable;
public Property(String name,Class<V> typ){ public Property(String name,Class<V> typ){
this.name=name; this.name=name;
this.typ=typ; this.typ=typ;
required=false;
writable=true;
}
@Override
public String toString(){
return String.format("%s:%s",name,typ.getSimpleName());
} }
public String getName(){return name;} public String getName(){return name;}
public Class<V> getTyp(){return typ;} public Class<V> getTyp(){return typ;}
public Property<V> setInitial(V val){initial=val;return this;}
public V getInitial(){return initial;}
public Property<V> setRequired(boolean f){required=f;return this;}
public boolean isRequired(){return required;}
public Property<V> setWritable(boolean f){writable=f;return this;}
public boolean isWritable(){return writable;}
public V get(Config store,V def){ public V get(Config store,V def){
return store.getProperty(this,def); return store.getProperty(this,def);
} }
public V get(Config store){ public V get(Config store){
return get(store,null); return get(store,initial);
} }
public void set(Config store,V val){ public void set(Config store,V val){
store.setProperty(this, val); store.setProperty(this, val);
} }
/** converts value such as string to expected type if possible. */
public V adaptValue(Object val){
val=Handy.normalize(this.getTyp(),val);
return this.getTyp().cast(val);
}
@Override
public int hashCode(){
return name.toLowerCase().hashCode();
}
@Override
public boolean equals(Object other){
if(other==this) return true;
if(other instanceof Property){
Property<?> pother=(Property<?>) other;
return name.equalsIgnoreCase(pother.getName()) && typ==pother.getTyp();
}
return false;
}
}
public static abstract class Base implements Config {
protected final HashMap<Property<?>,Object> props=new HashMap<>();
protected Object modified;
public boolean isModified(){
return modified!=null;
}
public Config setModified(Object p){
if(p==null){
// reset modified state
modified=null;
}else{
if(modified==null) modified=new TreeSet<Object>();
if(modified instanceof Collection){
((Collection)modified).add(p);
}else{
// modified is set already but not appendable
}
}
return this;
}
@Override
public Config clear(){
props.clear();
modified=null;
return this;
}
@Override
public <T> Config setProperty(Property<T> key, T val) {
setModified(key);
props.put(key,val);
return this;
}
@Override
public <T> T delProperty(Property<T> key) {
setModified(key);
Object val=props.remove(key);
return key.getTyp().cast(val);
}
} }
public static final Property<String> LOG_LEVEL=new Property<String>("LOG_LEVEL",String.class);
public static final Property<Logger> LOGGER=new Property<Logger>("LOGGER",Logger.class);
public void load(); public static final Property<String> LOG_LEVEL=new Property<>("LOG_LEVEL",String.class);
public void save(); public static final Property<Logger> LOGGER=new Property<>("LOGGER",Logger.class);
public static final Property<String> APP_INVOKED=new Property<>("APP_INVOKED",String.class);
public static final Property<String> APP_NAME=new Property<>("APP_NAME",String.class);
public static final Property<String> APP_TITLE=new Property<>("APP_TITLE",String.class);
public static final Property<String> APP_INFO=new Property<>("APP_INFO",String.class);
public static final Property<String> APP_WORKDIR=new Property<>("APP_WORKDIR",String.class);
public static final Property<String> APP_CLASS=new Property<>("APP_CLASS",String.class);
public static final Property<List> APP_ARGS=new Property<>("APP_ARGS",List.class);
public default Config getParent(){return null;};
public Config clear();
public Config load() throws IOException;
public Config save() throws IOException;
public String getId(); public String getId();
public <T> boolean hasProperty(Property<T> key);
public <T> Config setProperty(Property<T> key,T val); public <T> Config setProperty(Property<T> key,T val);
public <T> T getProperty(Property<T> key,T def); public <T> T getProperty(Property<T> key,T def);
public <T> T delProperty(Property<T> key);
public Config setSchema(Property<?> ...p);
} }
@@ -0,0 +1,118 @@
package com.reliancy.jabba;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import com.reliancy.jabba.sec.NotAuthentic;
import com.reliancy.jabba.sec.Secured;
import com.reliancy.jabba.sec.SecurityActor;
import com.reliancy.jabba.sec.SecurityPolicy;
import com.reliancy.jabba.ui.Feedback;
import com.reliancy.jabba.ui.FeedbackLine;
import com.reliancy.jabba.ui.Rendering;
import com.reliancy.jabba.ui.Template;
public class DemoEP implements AppModule{
@Override
public void publish(App app) {
app.getRouter().importMethods(this);
}
@Routed()
public String hello(){
Map<String, Object> context = new HashMap<>();
context.put("name", "Jared");
String ret="";
try {
Template t=Template.find("/templates/login.hbs");
System.out.println("Template:"+t);
ret = t.render(context).toString();
} catch (IOException e) {
e.printStackTrace();
}
return ret;
//#return "Hello World";
}
@Routed(
path="/helloPlain"
)
public void hello2(com.reliancy.jabba.Request req,Response resp) throws IOException{
resp.getEncoder().writeln("Hi There");
}
@Routed(
path="/hello3/{idd:int}"
)
public String hello3(int id){
return "Hello3:"+id;
}
@Routed(
path="/"
)
public String home(){
StringBuilder buf=new StringBuilder();
buf.append("<p>Sample pages:</p>");
buf.append("<dd><a href='/helloPlain'>plain</a></dd>");
buf.append("<dd><a href='/hello3/5'>parametric</a></dd>");
buf.append("<dd><a href='/hello'>templated</a></dd>");
buf.append("<dd><a href='/secured'>secured http</a></dd>");
buf.append("<dd><a href='/secured_form'>secured form</a></dd>");
return buf.toString();
}
@Routed
@Secured
public String secured(){
return "We are secured";
}
@Routed
@Secured(
login_form = "/login"
)
public String secured_form(){
return "We are secured by form";
}
@Routed
public void login(com.reliancy.jabba.Request req,Response resp){
//return "login form here";
if(req.getVerb().equals("POST")){
// here we need to process login and redirect
AppSession ass=AppSession.getInstance();
try{
System.out.println("Post login");
String userid=(String)req.getParam("userid",null);
String pwd=(String)req.getParam("password",null);
System.out.println("SS:"+ass);
System.out.println("P:"+userid+"/"+pwd);
SecurityPolicy secpol=ass.getApp().getSecurityPolicy();
SecurityActor user=secpol.authenticate(userid, pwd);
if(user==null) throw new NotAuthentic("invalid credentials");
resp.setStatus(Response.HTTP_FOUND_REDIRECT);
//String old_url=request.getPath();
//old_url=URLEncoder.encode(old_url,StandardCharsets.UTF_8.toString());
resp.setHeader("Location","/home");
}catch(Exception ex){
ass.getApp().log().error("error:",ex);
Feedback.get().push(FeedbackLine.error(ex.getLocalizedMessage()));
}
}
//Map<String, Object> context = new HashMap<>();
//context.put("app_title", "Jabba Login");
//context.put("name", "Jared");
//ArrayList<FeedbackLine> events=new ArrayList<>();
//Feedback.get().push(FeedbackLine.error("Error"));
//Feedback.get().push(FeedbackLine.info("Error"));
//Feedback.get().push(FeedbackLine.warn("Error"));
//context.put("feedback",events);
try {
resp.setContentType("text/html");
Rendering.begin("/templates/login.hbs")
//.with("feedback",events)
.end(resp);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
@@ -9,6 +9,9 @@ package com.reliancy.jabba;
import java.io.IOException; import java.io.IOException;
/** EndPoint is a special processor usually the last in chain.
*
*/
public abstract class EndPoint extends Processor{ public abstract class EndPoint extends Processor{
public EndPoint(String id) { public EndPoint(String id) {
@@ -7,46 +7,91 @@ You may not use this file except in compliance with the License.
*/ */
package com.reliancy.jabba; package com.reliancy.jabba;
import java.util.HashMap; import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
public class FileConfig implements Config{ public class FileConfig extends Config.Base{
final Config parent;
final ArrayList<Property<?>> schema=new ArrayList<>();
String path; String path;
final HashMap<String,Object> props=new HashMap<>(); public FileConfig(Config parent,String p){
public FileConfig(String p){ this.parent=parent;
path=p; path=p;
load();
} }
public FileConfig(){ public FileConfig(String p){
this(null); this(null,p);
} }
public void clear(){ public Config clear(){
props.clear(); props.clear();
return this;
} }
@Override @Override
public void load() { public Config getParent(){
return parent;
};
@Override
public Config load() throws IOException{
if(parent!=null) parent.load();
if(props.isEmpty()==false) return this; // not gona load again if loaded
return this;
} }
@Override @Override
public void save() { public Config save() throws IOException{
return this;
} }
@Override @Override
public String getId() { public String getId() {
return path; return path;
} }
public FileConfig setId(String path) {
public void setId(String path) {
this.path = path; this.path = path;
return this;
} }
@Override @Override
public <T> boolean hasProperty(Property<T> key){
if(props.containsKey(key)){
return true;
}else if(parent!=null && parent.hasProperty(key)){
return true;
}else{
return false;
}
}
/**
* FileConfig defers to parent first (it could be argsconfig) then returns own.
*/
@Override
public <T> T getProperty(Config.Property<T> key, T def) { public <T> T getProperty(Config.Property<T> key, T def) {
if(props.containsKey(key.getName())) return key.getTyp().cast(props.get(key.getName())); if(parent!=null && parent.hasProperty(key)){
else return def; return parent.getProperty(key, def);
}else if(props.containsKey(key)){
return key.getTyp().cast(props.get(key));
}else{
return def;
}
} }
/**
* FileConfig will save property localy so it is perserved even if not later provided.
*/
@Override @Override
public <T> Config setProperty(Config.Property<T> key, T val) { public <T> Config setProperty(Config.Property<T> key, T val) {
props.put(key.getName(),val); props.put(key,val);
return this;
}
@Override
public <T> T delProperty(Property<T> key) {
return (T)props.remove(key);
}
@Override
public Iterator<Property<?>> iterator() {
ArrayList<Property<?>> keys=new ArrayList<>(props.keySet());
return keys.iterator();
}
public Config setSchema(Property<?> ...p){
this.schema.clear();
for(Property<?> pp:p) schema.add(pp);
return this; return this;
} }
+135 -76
View File
@@ -10,70 +10,90 @@ import com.reliancy.util.Resources;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL; import java.net.URL;
import java.util.HashMap; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.stream.Stream; import org.slf4j.Logger;
public class FileServer extends EndPoint implements Resources.PathRewrite{ /** FileServer is an module and endpoint that exposes multiple URLs thru which files are served.
public static interface Filter{ * First it will be just get(tting), later
boolean isAcceptable(String path); * TODO: putting, posting and maybe full DAV.
* TODO: We will need proper security.
* TODO: We will also add in memory serving.
* Please note Router is for routing.
* Bucket is there to process input/output given verbs over resources under it.
*/
public class FileServer extends EndPoint implements AppModule,Resources.PathRewrite{
/** Bucket interface to abstract i/o and provide easier extensibility.
* asContainer matches path and then returns local-to-packet path.
*/
public static interface Bucket{
String getPrefix();
String asContained(String path);
boolean equals(String pref);
InputStream openSource(String local_path,FileServer user) throws IOException;
OutputStream openSink(String local_path,FileServer user) throws IOException;
} }
public static class ExtFilter implements Filter{ public static class FileBucket implements Bucket{
final String[] allowed; final String prefix;
public ExtFilter(String ...ext){allowed=ext;} String[] extAllowed;
Object[] domain;
public FileBucket(String prefix){
this.prefix=prefix;
extAllowed=new String[]{};
domain=new Object[]{};
}
@Override @Override
public boolean isAcceptable(String path) { public final String getPrefix(){return prefix;}
if(allowed.length==0) return true; @Override
for(String ext:allowed) if(path.endsWith(ext)) return true; public String asContained(String path) {
if(!path.startsWith(prefix)) return null; // not contained
String local_path=path.replace(prefix,"");
if(extAllowed.length==0) return local_path;
for(String ext:extAllowed){
if(path.endsWith(ext)){
return local_path;
}
}
return null;
}
@Override
public boolean equals(Object o){
if(o instanceof FileBucket) return prefix.equals(((FileBucket)o).getPrefix());
if(o instanceof String) return prefix.equals((String)o);
return false; return false;
} }
} @Override
public static Filter NOFILTER=new ExtFilter(); public boolean equals(String pref){return prefix.equals(pref);}
String diskPrefix; public FileBucket setDomain(Object...sp){
String classPrefix; domain=sp;
final HashMap<String,Object[]> map; return this;
final HashMap<String,Filter> filt; }
public FileServer(String url_path,Filter f,Object ... disk_path){ public Object[] getDomain(){
super("fileserver"); return (domain!=null && domain.length>0)?domain:Resources.search_path;
diskPrefix=classPrefix=null; }
filt=new HashMap<>(); public InputStream openSource(String local_path,FileServer user) throws IOException{
map=new HashMap<>(); Object[] sp=getDomain();
addRoute(url_path,f, disk_path); URL f=Resources.findFirst(user,local_path, sp);
} if(f==null) return null; // skip if rpath not located
public FileServer(String url_path,Object ... disk_path){ return f.openStream();
this(url_path,NOFILTER,disk_path); }
} public OutputStream openSink(String local_path,FileServer user) throws IOException{
@Override return null;
public void serve(Request request, Response response) throws IOException {
String path=request.getPath();
log().debug("to serve:"+path);
for(String prefix:map.keySet()){
boolean match=path.startsWith(prefix);
if(match){
Object[] sp=getSearchPath(prefix);
String rpath=path.replace(prefix,"");
if(!filt.get(prefix).isAcceptable(rpath)) continue; // not acceptable to filter
URL f=Resources.findFirst(this, rpath, sp);
if(f==null) continue; // skip if rpath not located
this.log().debug("\tfound:"+f);
writeResource(f,response);
return;
}
} }
response.setStatus(Response.HTTP_NOT_FOUND);
response.getEncoder().writeln("missing file:{0}",path);
this.log().error("not found:"+path);
} }
/** final ArrayList<Bucket> buckets=new ArrayList<>();
* we prefix our path for disk and class contexts. String diskPrefix; // will be prefixed to source if file
*/ String classPrefix; // will be prefixed to source if class
@Override String urlPrefix; // will be prefixed to source if URL
public String rewritePath(String path, Object context) { public FileServer(String url_path,String offset,Object ... source){
if(diskPrefix!=null && context instanceof String) return this.diskPrefix+path; super(null);
if(diskPrefix!=null && context instanceof File) return this.diskPrefix+path; diskPrefix=classPrefix=offset;
if(classPrefix!=null && context instanceof Class) return this.classPrefix+path; addBucket(new FileBucket(url_path).setDomain(source));
return path; }
public FileServer(){
super(null);
} }
public FileServer setDiskPrefix(String prefix){ public FileServer setDiskPrefix(String prefix){
diskPrefix=prefix; diskPrefix=prefix;
@@ -83,12 +103,50 @@ public class FileServer extends EndPoint implements Resources.PathRewrite{
classPrefix=prefix; classPrefix=prefix;
return this; return this;
} }
public FileServer setURLOffset(String offset){
urlPrefix=offset;
return this;
}
/** /**
* Will render a file to response. * we prefix our path for disk and class contexts.
*/
@Override
public String rewritePath(String path, Object context) {
if(diskPrefix!=null && context instanceof String) return this.diskPrefix+path;
if(diskPrefix!=null && context instanceof File) return this.diskPrefix+path;
if(classPrefix!=null && context instanceof Class) return this.classPrefix+path;
if(urlPrefix!=null && context instanceof URL) return this.urlPrefix+path;
return path;
}
@Override
public void serve(Request request, Response response) throws IOException {
String path=request.getPath();
Logger logger=log();
boolean atDebug=logger.isDebugEnabled();
if(atDebug) logger.debug("to serve:"+path);
for(Bucket bucket:buckets){
String local_path=bucket.asContained(path);
if(local_path==null) continue; // this bucket is not accepting
try(InputStream ins=bucket.openSource(local_path,this)){
if(atDebug) logger.debug("\tfound:"+local_path);
String ctype=HTTP.ext2mime(local_path);
response.setStatus(Response.HTTP_OK);
response.setContentType(ctype);
ResponseEncoder enc=response.getEncoder();
enc.writeStream(ins);
return;
}
}
response.setStatus(Response.HTTP_NOT_FOUND);
response.getEncoder().writeln("missing file:{0}",path);
logger.error("not found:"+path);
}
/**
* Will render a URL resource to response.
* @param f * @param f
* @param response * @param response
*/ */
protected void writeResource(URL f, Response response) throws IOException{ protected static void writeResource(URL f, Response response) throws IOException{
//log().info("writing:"+f); //log().info("writing:"+f);
ResponseEncoder enc=response.getEncoder(); ResponseEncoder enc=response.getEncoder();
try(InputStream is=f.openStream()){ try(InputStream is=f.openStream()){
@@ -98,28 +156,29 @@ public class FileServer extends EndPoint implements Resources.PathRewrite{
enc.writeStream(is); enc.writeStream(is);
} }
} }
public final void addRoute(String url_path,Filter f,Object... disk_path){ /** adds a route which serves files.
if(disk_path!=null){ * if disk_path is ommited (0 len) or null we use Resources.search_path.
map.put(url_path,disk_path); * @param url_path
filt.put(url_path,f!=null?f:NOFILTER); * @param f
}else{ * @param disk_path search path for resource
map.remove(url_path); */
filt.remove(url_path); public final FileServer addBucket(Bucket bucket){
} buckets.add(bucket);
return this;
} }
public Object[] getSearchPath(String url_path){ public FileServer delBucket(Bucket bucket){
return map.get(url_path); buckets.remove(bucket);
return this;
} }
public Filter getFilter(String url_path){ public Bucket getBucket(String url_path){
return filt.get(url_path); for(Bucket b:buckets) if(b.equals(url_path)) return b;
return null;
} }
public Stream<String> streamRoutes() { public Iterator<Bucket> enumBuckets(){
return map.keySet().stream(); return buckets.iterator();
} }
public Iterator<String> enumRoutes(){ public void publish(App app) {
return map.keySet().iterator(); Router rep=app.getRouter();
} for(Bucket b:buckets) rep.addRoute("GET",b.getPrefix()+".*",this);
public void exportRoutes(RoutedEndPoint rep) {
streamRoutes().forEach(up->rep.addRoute("GET",up+".*",this));
} }
} }
+31 -10
View File
@@ -11,8 +11,15 @@ import java.io.File;
import java.net.URL; import java.net.URL;
import java.util.HashMap; import java.util.HashMap;
/** HTTP related methods and classes. */ /** HTTP related methods and classes.
*
*/
public final class HTTP { public final class HTTP {
public static String MIME_PLAIN="text/plain";
public static String MIME_JSON="application/json";
public static String MIME_BYTES="application/octet-stream";
public static String MIME_HTML="text/html";
public static HashMap<String,String> MIME_MAP=new HashMap<>(); public static HashMap<String,String> MIME_MAP=new HashMap<>();
public static class Header{ public static class Header{
public String key; public String key;
@@ -30,6 +37,11 @@ public final class HTTP {
key=k;value=v;this.maxAge=maxAge;secure=sec; key=k;value=v;this.maxAge=maxAge;secure=sec;
} }
} }
/** maps extension to mime type.
* it will extract everything after last dot so we can pass a path too.
* @param ext
* @return
*/
public static String ext2mime(String ext){ public static String ext2mime(String ext){
if(MIME_MAP.isEmpty()){ if(MIME_MAP.isEmpty()){
MIME_MAP.put("ico","image/x-icon"); MIME_MAP.put("ico","image/x-icon");
@@ -42,35 +54,44 @@ public final class HTTP {
MIME_MAP.put("jpeg","image/jpeg"); MIME_MAP.put("jpeg","image/jpeg");
MIME_MAP.put("png","image/png"); MIME_MAP.put("png","image/png");
MIME_MAP.put("webp","image/webp"); MIME_MAP.put("webp","image/webp");
MIME_MAP.put("txt","text/plain"); MIME_MAP.put("txt",MIME_PLAIN);
MIME_MAP.put("css","text/css"); MIME_MAP.put("css","text/css");
MIME_MAP.put("csv","text/csv"); MIME_MAP.put("csv","text/csv");
MIME_MAP.put("html","text/html"); MIME_MAP.put("html",MIME_HTML);
MIME_MAP.put("htm","text/html"); MIME_MAP.put("htm",MIME_HTML);
MIME_MAP.put("xml","text/xml"); MIME_MAP.put("xml","text/xml");
MIME_MAP.put("json",MIME_JSON);
} }
ext=ext.substring(ext.lastIndexOf(".")+1).toLowerCase();
return MIME_MAP.get(ext); return MIME_MAP.get(ext);
} }
/** guesses mime type based on content.
* for url,path or file looks at the path otherwise it examines content.
* if you pass in charsequence it will look for indicators of html or json.
* for bytes nothing yet but we could examine headers to images.
* @param ret
* @return
*/
public static String guess_mime(Object ret) { public static String guess_mime(Object ret) {
if(ret instanceof CharSequence){ if(ret instanceof CharSequence){
CharSequence retstr=(CharSequence)ret; CharSequence retstr=(CharSequence)ret;
for(int index=0;index<retstr.length();index++){ for(int index=0;index<retstr.length();index++){
char ch=retstr.charAt(index); char ch=retstr.charAt(index);
if(Character.isWhitespace(ch)) continue; if(Character.isWhitespace(ch)) continue;
if(ch=='<') return "text/html"; if(ch=='<') return MIME_HTML;
if(ch=='{' || ch=='[') return "application/json"; if(ch=='{' || ch=='[') return MIME_JSON;
break; break;
} }
return "text/plain"; return MIME_PLAIN;
} }
if(ret instanceof byte[]){ if(ret instanceof byte[]){
return "application/octet-stream"; return MIME_BYTES;
} }
if(ret instanceof File || ret instanceof URL){ if(ret instanceof File || ret instanceof URL || ret instanceof Path){
String path=String.valueOf(ret); String path=String.valueOf(ret);
String ext=path.substring(path.lastIndexOf(".")+1).toLowerCase(); String ext=path.substring(path.lastIndexOf(".")+1).toLowerCase();
String mime=ext2mime(ext); String mime=ext2mime(ext);
return mime!=null?mime:"application/octet-stream"; return mime!=null?mime:MIME_BYTES;
} }
return null; return null;
} }
+84 -160
View File
@@ -10,20 +10,15 @@ package com.reliancy.jabba;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.EventListener; import java.util.EventListener;
import java.util.HashMap;
import java.util.Map;
import com.reliancy.jabba.sec.NotAuthentic;
import com.reliancy.jabba.sec.Secured;
import com.reliancy.jabba.sec.SecurityActor;
import com.reliancy.jabba.sec.SecurityPolicy; import com.reliancy.jabba.sec.SecurityPolicy;
import com.reliancy.jabba.sec.plain.PlainSecurityStore; import com.reliancy.jabba.sec.plain.PlainSecurityStore;
import com.reliancy.jabba.ui.Feedback;
import com.reliancy.jabba.ui.FeedbackLine;
import com.reliancy.jabba.ui.Menu; import com.reliancy.jabba.ui.Menu;
import com.reliancy.jabba.ui.MenuItem; import com.reliancy.jabba.ui.MenuItem;
import com.reliancy.jabba.ui.Rendering; import com.reliancy.jabba.ui.Rendering;
import com.reliancy.jabba.ui.Template; import com.reliancy.jabba.ui.Template;
import com.reliancy.util.Log;
import com.reliancy.util.Resources;
import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Handler;
@@ -59,6 +54,14 @@ public class JettyApp extends App implements Handler{
_state=State.STOPPED; _state=State.STOPPED;
} }
/** implementation of jetty handler interface */ /** implementation of jetty handler interface */
public Connector[] getConnectors(){
if(connectors!=null) return connectors;
ServerConnector connector = new ServerConnector(jetty);
connector.setReuseAddress(false);
connector.setPort(8090);
connectors=new Connector[] {connector};
return connectors;
}
@Override @Override
public Server getServer() { public Server getServer() {
return jetty; return jetty;
@@ -66,10 +69,12 @@ public class JettyApp extends App implements Handler{
@Override @Override
public void setServer(Server arg0) { public void setServer(Server arg0) {
System.out.println("setServer..."+jetty+"/"+arg0);
jetty=arg0; jetty=arg0;
} }
@Override @Override
public boolean addEventListener(EventListener arg0) { public boolean addEventListener(EventListener arg0) {
System.out.println("adding evt listener...");
return false; return false;
} }
@Override @Override
@@ -111,11 +116,28 @@ public class JettyApp extends App implements Handler{
@Override @Override
public void start() throws Exception { public void start() throws Exception {
_state=State.STARTED; _state=State.STARTED;
jetty.setConnectors(getConnectors());
} }
@Override @Override
public void stop() throws Exception { public void stop() throws Exception {
_state=State.STOPPED; _state=State.STOPPED;
Connector[] connectors=jetty.getConnectors();
if(connectors==null || connectors.length==0) return;
for(Connector c:connectors){
ServerConnector cc=(ServerConnector) c;
//System.out.println("stopping connecor:"+cc);
try{
cc.stop();
cc.getConnectedEndPoints().forEach((endpoint)-> {
//System.out.println("closing endpoint:"+endpoint);
endpoint.close();
});
}finally{
cc.close();
//System.out.println("closing connecor:"+cc.getState());
}
}
} }
@Override @Override
@@ -132,7 +154,6 @@ public class JettyApp extends App implements Handler{
HttpServletResponse response) HttpServletResponse response)
throws IOException throws IOException
{ {
baseRequest.setHandled(true);
com.reliancy.jabba.Request req=new com.reliancy.jabba.Request(request); com.reliancy.jabba.Request req=new com.reliancy.jabba.Request(request);
Response resp=new Response(response); Response resp=new Response(response);
@@ -143,40 +164,32 @@ public class JettyApp extends App implements Handler{
}catch(IOException ioex){ }catch(IOException ioex){
Template t=Template.find("/templates/error.hbs"); Template t=Template.find("/templates/error.hbs");
if(t==null) throw ioex; if(t==null) throw ioex;
Rendering.begin(t) Rendering.begin(t).with(ioex).end(resp);
.with(ioex)
.end(resp.getEncoder().getWriter());
log().error("error:",ioex); log().error("error:",ioex);
}catch(RuntimeException rex){ }catch(RuntimeException rex){
Template t=Template.find("/templates/error.hbs"); Template t=Template.find("/templates/error.hbs");
if(t==null) throw rex; if(t==null) throw rex;
Rendering.begin(t) Rendering.begin(t).with(rex).end(resp);
.with(rex)
.end(resp.getEncoder().getWriter());
log().error("error:",rex); log().error("error:",rex);
}finally{ }finally{
baseRequest.setHandled(true);
ss.end(); ss.end();
} }
} }
/** our own interface specific to jetty engine*/ /** our own interface specific to jetty engine*/
public Connector[] getConnectors(){
if(connectors!=null) return connectors;
ServerConnector connector = new ServerConnector(jetty);
connector.setReuseAddress(false);
connector.setPort(8090);
connectors=new Connector[] {connector};
return connectors;
}
public void begin(Config conf) throws Exception{ public void begin(Config conf) throws Exception{
// step 2: configure application, might add processors, adjust config
configure(conf);
// step 1: install config then begin by signaling all middleware
super.begin(conf); super.begin(conf);
jetty.setConnectors(getConnectors()); // step 2: start jetty
try{ try{
log().info("starting...");
jetty.start(); jetty.start();
}catch(Exception ex){ }catch(Exception ex){
setState(State.FAILED); setState(State.FAILED);
if(ex.getCause() instanceof java.net.BindException){ if(ex.getCause() instanceof java.net.BindException){
log().error("Bind issue",ex); log().error("bind issue",ex);
Thread.sleep(3000); Thread.sleep(3000);
}else throw ex; }else throw ex;
} }
@@ -186,150 +199,61 @@ public class JettyApp extends App implements Handler{
if(jetty!=null) jetty.join(); if(jetty!=null) jetty.join();
} }
public void end() throws Exception{ public void end() throws Exception{
//setState(State.STOPPING);
super.end(); super.end();
Connector[] connectors=jetty.getConnectors(); Log.cleanup(); // release logging in case we deferred
// System.out.println(connectors); System.gc(); // sweep memory just in caser
if(connectors!=null) for(Connector c:connectors){
ServerConnector cc=(ServerConnector) c;
//System.out.println("stopping connecor:"+cc);
try{
cc.stop();
cc.getConnectedEndPoints().forEach((endpoint)-> {
//System.out.println("closing endpoint:"+endpoint);
endpoint.close();
});
}finally{
cc.close();
//System.out.println("closing connecor:"+cc.getState());
}
}
//System.out.println("signaling...");
jetty.stop();
//setState(State.STOPPED);
//System.out.println("cleanup...");
System.gc();
//System.out.println("return...");
} }
public static void main( String[] args ) throws Exception{ /** called from begin just before jetty starts.
//System.out.println("Hello World!"); * this method is called before middleware is notified so we can add or adjust config.
//String rt=new File(".").getAbsolutePath(); * override to hook up your application.
//System.out.println("ROOT:"+rt); * normally follows configuraion and does common sense steps.
String work_dir="./var"; * might install middleware (processors) which are later passed config.
if(new File(work_dir).exists()==false){ */
work_dir="../var"; public void configure(Config conf) throws Exception{
} App app=this;
Template.search_path(work_dir,App.class); // setup global search path - include workdir first, then get class and app.class
JettyApp app=new JettyApp(); Class<?> cls=getClass();
if(cls!=JettyApp.class) Resources.appendSearch(0,JettyApp.class);
Resources.appendSearch(0,cls);
String work_dir=ArgsConfig.APP_WORKDIR.get(conf);
if(work_dir!=null) Resources.appendSearch(0,work_dir);
//for(Object p:Resources.search_path){
// System.out.println("sp:"+p);
//}
//Template.search_path(work_dir,App.class); -- not needed anymore
// install app session middleware
app.addAppSession(); app.addAppSession();
// set security policy
SecurityPolicy secpol=new SecurityPolicy().setStore(new PlainSecurityStore()); SecurityPolicy secpol=new SecurityPolicy().setStore(new PlainSecurityStore());
app.setSecurityPolicy(secpol); app.setSecurityPolicy(secpol);
RoutedEndPoint rep=new RoutedEndPoint().importMethods(app); // install router
app.setRouter(rep); app.setRouter(new Router());
FileServer fs=new FileServer("/static",work_dir+"/public"); DemoEP ep=new DemoEP();
fs.exportRoutes(app.getRouter()); ep.publish(app);
// install file sever endpoint
FileServer fs=new FileServer("/static","/public");
fs.publish(app);
Menu top_menu=Menu.request(Menu.TOP); Menu top_menu=Menu.request(Menu.TOP);
top_menu.add(new MenuItem("home")).addSpacer().add(new MenuItem("login")); top_menu.add(new MenuItem("home")).addSpacer().add(new MenuItem("login"));
top_menu.setTitle("Jabba"); top_menu.setTitle("Jabba3");
app.run(new FileConfig());
//System.out.println("Goodbye World!");
} }
public static void main( String[] args ) throws Exception{
@Routed() Config cnf=new ArgsConfig(args).load();
public String hello(){ JettyApp app=new JettyApp();
Map<String, Object> context = new HashMap<>(); Runtime.getRuntime().addShutdownHook(new Thread(() -> {
context.put("name", "Jared"); if(app.isRunning()){
String ret=""; try {
try { app.jetty.stop();
Template t=Template.find("/templates/login.hbs"); synchronized(app){
System.out.println("Template:"+t); app.wait(5000);
ret = t.render(context).toString(); }
} catch (IOException e) { } catch (Exception e) {
e.printStackTrace(); app.log().error("shutdown cleanup:", e);
} }
return ret;
//#return "Hello World";
}
@Routed(
path="/helloPlain"
)
public void hello2(com.reliancy.jabba.Request req,Response resp) throws IOException{
resp.getEncoder().writeln("Hi There");
}
@Routed(
path="/hello3/{idd:int}"
)
public String hello3(int id){
return "Hello3:"+id;
}
@Routed(
path="/"
)
public String home(){
StringBuilder buf=new StringBuilder();
buf.append("<p>Sample pages:</p>");
buf.append("<dd><a href='/helloPlain'>plain</a></dd>");
buf.append("<dd><a href='/hello3/5'>parametric</a></dd>");
buf.append("<dd><a href='/hello'>templated</a></dd>");
buf.append("<dd><a href='/secured'>secured http</a></dd>");
buf.append("<dd><a href='/secured_form'>secured form</a></dd>");
return buf.toString();
}
@Routed
@Secured
public String secured(){
return "We are secured";
}
@Routed
@Secured(
login_form = "/login"
)
public String secured_form(){
return "We are secured by form";
}
@Routed
public void login(com.reliancy.jabba.Request req,Response resp){
//return "login form here";
if(req.getVerb().equals("POST")){
// here we need to process login and redirect
try{
System.out.println("Post login");
String userid=(String)req.getParam("userid",null);
String pwd=(String)req.getParam("password",null);
AppSession ass=AppSession.getInstance();
System.out.println("SS:"+ass);
System.out.println("P:"+userid+"/"+pwd);
SecurityPolicy secpol=ass.getApp().getSecurityPolicy();
SecurityActor user=secpol.authenticate(userid, pwd);
if(user==null) throw new NotAuthentic("invalid credentials");
resp.setStatus(Response.HTTP_FOUND_REDIRECT);
//String old_url=request.getPath();
//old_url=URLEncoder.encode(old_url,StandardCharsets.UTF_8.toString());
resp.setHeader("Location","/home");
}catch(Exception ex){
log().error("error:",ex);
Feedback.get().push(FeedbackLine.error(ex.getLocalizedMessage()));
} }
} }));
//Map<String, Object> context = new HashMap<>(); app.run(cnf);
//context.put("app_title", "Jabba Login");
//context.put("name", "Jared");
//ArrayList<FeedbackLine> events=new ArrayList<>();
//Feedback.get().push(FeedbackLine.error("Error"));
//Feedback.get().push(FeedbackLine.info("Error"));
//Feedback.get().push(FeedbackLine.warn("Error"));
//context.put("feedback",events);
try {
resp.setContentType("text/html");
Rendering.begin("/templates/login.hbs")
//.with("feedback",events)
.end(resp.getEncoder().getWriter());
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
} }
} }
@@ -10,7 +10,12 @@ import java.io.IOException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** Abstract base class of request/response handlers.
* App is a processor and under it a router and a chain of filters are also processors.
* Also endpoints are processors too.
*/
public abstract class Processor { public abstract class Processor {
protected Processor parent;
protected Processor next; protected Processor next;
protected String id; protected String id;
protected boolean active; protected boolean active;
@@ -32,21 +37,30 @@ public abstract class Processor {
public void setNext(Processor next) { public void setNext(Processor next) {
this.next = next; this.next = next;
} }
public Processor getParent() {
return parent;
}
public void setParent(Processor p) {
if(parent!=null && p!=null && p!=parent){
throw new IllegalStateException("processor is attached to different parent");
}
this.parent = p;
}
public boolean isActive() { public boolean isActive() {
return active; return active;
} }
public void setActive(boolean active) { public void setActive(boolean active) {
this.active = active; this.active = active;
} }
public Config getConfig() { public Config getConfig() {
return config; if(config!=null) return config;
if(parent!=null) return parent.getConfig();
return null;
} }
/* // using config as a marker of a run so set during begin
public void setConfig(Config config) { // public void setConfig(Config config) {
this.config = config; // this.config = config;
} // }
*/
/** /**
* Main event processing chain. * Main event processing chain.
* Will go down the chain until result code is set. * Will go down the chain until result code is set.
@@ -70,9 +84,19 @@ public abstract class Processor {
ss.leave(this); ss.leave(this);
} }
} }
/** Place to prepare for a run. */
public void begin(Config conf) throws Exception{ public void begin(Config conf) throws Exception{
this.config=conf; this.config=conf;
} }
/** Special null config begin only useful for middleware (to force them to use parent). */
protected void begin() throws Exception {
this.begin(null);
}
/**
* cleans up by detaching from config.
* Also notifies any waiting clients that it is done.
* @throws Exception
*/
public void end() throws Exception{ public void end() throws Exception{
this.config=null; this.config=null;
} }
@@ -32,7 +32,7 @@ public class Request {
return http_request.getMethod(); return http_request.getMethod();
} }
/** /**
* Look for this parameter in pathParan, queryParams and forms. * Look for this parameter in pathParam, queryParams and forms.
* @param pname * @param pname
* @return * @return
*/ */
@@ -8,6 +8,7 @@ You may not use this file except in compliance with the License.
package com.reliancy.jabba; package com.reliancy.jabba;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@@ -15,6 +16,7 @@ import java.io.OutputStreamWriter;
import java.io.Reader; import java.io.Reader;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.Writer; import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.Collection; import java.util.Collection;
@@ -25,22 +27,30 @@ import java.util.Locale;
* This class will replace the Java writer. * This class will replace the Java writer.
* It will have chainable calls. It will inherit lower level calls * It will have chainable calls. It will inherit lower level calls
* and then extend with higher level. For example write, writeln but then writeJson etc. * and then extend with higher level. For example write, writeln but then writeJson etc.
* implements closeable,autocloseable,appendable.
* we do not close
*/ */
public class ResponseEncoder { public class ResponseEncoder implements Appendable,Closeable{
protected final Response response; protected final Response response;
protected final Locale locale; //protected final Locale locale;
protected Writer writer; protected Writer writer;
protected OutputStream out; protected OutputStream out;
protected Charset charSet;
public ResponseEncoder(Response r){ public ResponseEncoder(Response r){
response=r; this(r,StandardCharsets.UTF_8);
locale=Locale.getDefault(); //response=r;
//locale=Locale.getDefault();
} }
public ResponseEncoder(Response r,Locale loc){ public ResponseEncoder(Response r,Charset chset){
response=r; response=r;
locale=loc; //locale=loc;
charSet=StandardCharsets.UTF_8;
} }
protected OutputStream getOutputStream() throws IOException{ public ResponseEncoder setCharSet(Charset set){
charSet=set;
return this;
}
public OutputStream getOutputStream() throws IOException{
if(out!=null) return out; if(out!=null) return out;
if(response.getStatus()==null) response.setStatus(Response.HTTP_OK); if(response.getStatus()==null) response.setStatus(Response.HTTP_OK);
if(response.getContentType()==null) response.setContentType("application/octet-stream"); if(response.getContentType()==null) response.setContentType("application/octet-stream");
@@ -51,10 +61,10 @@ public class ResponseEncoder {
}else{ }else{
out=new ByteArrayOutputStream(); out=new ByteArrayOutputStream();
} }
writer=new OutputStreamWriter(out,StandardCharsets.UTF_8); writer=new OutputStreamWriter(out,charSet);
return out; return out;
} }
protected Writer getWriter() throws IOException{ public Writer getWriter() throws IOException{
if(writer!=null) return writer; if(writer!=null) return writer;
if(response.getStatus()==null) response.setStatus(Response.HTTP_OK); if(response.getStatus()==null) response.setStatus(Response.HTTP_OK);
if(response.getContentType()==null) response.setContentType("text/plain;charset=utf-8"); if(response.getContentType()==null) response.setContentType("text/plain;charset=utf-8");
@@ -64,7 +74,7 @@ public class ResponseEncoder {
writer=response.char_response; writer=response.char_response;
}else if(response.byte_response!=null){ }else if(response.byte_response!=null){
out=response.byte_response; out=response.byte_response;
writer=new OutputStreamWriter(out,StandardCharsets.UTF_8); writer=new OutputStreamWriter(out,charSet);
}else{ }else{
writer=new StringWriter(); writer=new StringWriter();
} }
@@ -132,4 +142,23 @@ public class ResponseEncoder {
//wr.append("\n"); //wr.append("\n");
return this; return this;
} }
////// Interface implementations
@Override
public void close() throws IOException {
getWriter().close();
}
@Override
public Appendable append(CharSequence csq) throws IOException {
return append(csq,0,csq.length());
}
@Override
public Appendable append(CharSequence csq, int start, int end) throws IOException {
this.getWriter().append(csq,start,end);
return this;
}
@Override
public Appendable append(char c) throws IOException {
this.getWriter().append(c);
return this;
}
} }
@@ -18,16 +18,24 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class RoutedEndPoint extends EndPoint{ /** Router is a special Processor which redirects requests to endpoints.
*
*/
public class Router extends Processor{
HashMap<String,EndPoint> routes=new HashMap<>(); // route pattern to endpoint HashMap<String,EndPoint> routes=new HashMap<>(); // route pattern to endpoint
ArrayList<RouteDetector> detectors=new ArrayList<>(); // route patterns ordered ArrayList<RouteDetector> detectors=new ArrayList<>(); // route patterns ordered
int[] indexes; // indexes for each route within regex int[] indexes; // indexes for each route within regex
Pattern regex; Pattern regex;
public RoutedEndPoint() { public Router() {
super("router"); super("router");
} }
@Override
public void before(Request request, Response response) throws IOException {
}
@Override
public void after(Request request, Response response) throws IOException {
}
@Override @Override
public void serve(Request req, Response resp) throws IOException { public void serve(Request req, Response resp) throws IOException {
//System.out.println(req.http_request); //System.out.println(req.http_request);
@@ -135,7 +143,7 @@ public class RoutedEndPoint extends EndPoint{
* @param target * @param target
* @return * @return
*/ */
public RoutedEndPoint importMethods(Object target){ public Router importMethods(Object target){
//RoutedEndPoint ret=new RoutedEndPoint(); //RoutedEndPoint ret=new RoutedEndPoint();
LinkedList<Method> routes=new LinkedList<>(); LinkedList<Method> routes=new LinkedList<>();
Class<?> type=target.getClass(); Class<?> type=target.getClass();
@@ -37,6 +37,10 @@ public class Menu extends MenuItem{
public int getSize(){ public int getSize(){
return items.size(); return items.size();
} }
public Menu clear(){
items.clear();
return this;
}
public Menu add(MenuItem itm){ public Menu add(MenuItem itm){
items.add(itm); items.add(itm);
return this; return this;
@@ -8,11 +8,11 @@ You may not use this file except in compliance with the License.
package com.reliancy.jabba.ui; package com.reliancy.jabba.ui;
import java.io.IOException; import java.io.IOException;
import java.io.Writer;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.github.jknack.handlebars.Context; import com.github.jknack.handlebars.Context;
import com.reliancy.jabba.Response;
import com.reliancy.util.CodeException; import com.reliancy.util.CodeException;
/** /**
@@ -26,6 +26,7 @@ public class Rendering extends HashMap<String,Object>{
ret.with("menu",Menu.request(Menu.TOP)); ret.with("menu",Menu.request(Menu.TOP));
ret.with("toolbar",Menu.request(Menu.LEFT)); ret.with("toolbar",Menu.request(Menu.LEFT));
ret.with("feedback",Feedback.get()); ret.with("feedback",Feedback.get());
ret.with("layout","land-app");
return ret; return ret;
} }
public static Rendering begin(String path,Object ...sp){ public static Rendering begin(String path,Object ...sp){
@@ -48,9 +49,9 @@ public class Rendering extends HashMap<String,Object>{
ctx.destroy(); ctx.destroy();
} }
} }
public void end(Writer _out) throws IOException{ public void end(Response resp) throws IOException{
try{ try{
template.render(ctx,_out); template.render(ctx,resp.getEncoder().getWriter());
}finally{ }finally{
ctx.destroy(); ctx.destroy();
} }
@@ -59,16 +59,29 @@ public class Template {
@Override @Override
public TemplateSource sourceAt(String location) throws IOException { public TemplateSource sourceAt(String location) throws IOException {
String fullpath=this.resolve(location); String fullpath=this.resolve(location);
URL loc=Resources.findFirst(null,fullpath,Template.search_path); URL loc=Resources.findFirst(null,fullpath,Template.search_path());
//System.out.println(location+":"+loc+":"+fullpath); //System.out.println(location+":"+loc+":"+fullpath);
if (loc == null) { if (loc == null) {
Logger.getLogger(Template.class.getSimpleName()).warning("Missing template"+fullpath); Logger.getLogger(Template.class.getSimpleName()).warning("Template missing:"+fullpath);
throw new FileNotFoundException(location); throw new FileNotFoundException(location);
} }
return new URLTemplateSource(location,loc); return new URLTemplateSource(location,loc);
} }
public String resolve(String uri){
if(uri==null || uri.isEmpty()) return uri;
// we strip prefix and suffic just in case
if(uri.startsWith(this.getPrefix())) uri=uri.substring(this.getPrefix().length());
if(uri.endsWith(this.getSuffix())) uri=uri.substring(0,uri.indexOf(this.getSuffix()));
if(Template.partial_map.containsKey(uri)){
uri=Template.partial_map.get(uri);
}
return super.resolve(uri);
}
} }
static Handlebars handlebars; static Handlebars handlebars;
static HashMap<String,String> partial_map=new HashMap<>();
static Object[] search_path;
static HashMap<String,Template> cache=new HashMap<>();
static{ static{
handlebars= new Handlebars(new HBLoader()); handlebars= new Handlebars(new HBLoader());
StringHelpers.register(handlebars); StringHelpers.register(handlebars);
@@ -77,10 +90,8 @@ public class Template {
} }
*/ */
partial_map.put("__frame__","frame-land");
} }
static Object[] search_path;
static HashMap<String,Template> cache=new HashMap<>();
/** renders a template to string, possibly locates it first. /** renders a template to string, possibly locates it first.
* *
* @param path * @param path
@@ -89,7 +100,7 @@ public class Template {
* @throws IOException * @throws IOException
*/ */
public static CharSequence render(String path,Map<String,?> context) throws IOException{ public static CharSequence render(String path,Map<String,?> context) throws IOException{
Template t=find(path,search_path); Template t=find(path);
if(t==null){ if(t==null){
return null; return null;
}else{ }else{
@@ -102,7 +113,7 @@ public class Template {
public static Template find(String path,Object ... sp) { public static Template find(String path,Object ... sp) {
Template ret=cache.get(path); Template ret=cache.get(path);
if(ret!=null) return ret; if(ret!=null) return ret;
URL loc=Resources.findFirst(null, path, (sp!=null && sp.length>0?sp:search_path)); URL loc=Resources.findFirst(null, path, (sp!=null && sp.length>0?sp:search_path()));
if(loc==null) return null; if(loc==null) return null;
ret=new Template(loc); ret=new Template(loc);
cache.put(path,ret); cache.put(path,ret);
@@ -110,7 +121,17 @@ public class Template {
} }
public static Object[] search_path(Object...sp){ public static Object[] search_path(Object...sp){
if(sp!=null && sp.length>0) search_path=sp; if(sp!=null && sp.length>0) search_path=sp;
return search_path; return search_path!=null?search_path:Resources.search_path;
}
/**
* will register a partial mapping to let us dynamically switch partials.
* this is useful in writing components for various layouts.
* all components derive from frame while frame is switched to frame-dash or frame-land.
* @param src
* @param dst
*/
public static void remap_partial(String src,String dst){
Template.partial_map.put(src,dst);
} }
public static final int ERR_BADTEMPLATE=ResultCode.defineFailure(0x01,Template.class,"bad template: ${template}"); public static final int ERR_BADTEMPLATE=ResultCode.defineFailure(0x01,Template.class,"bad template: ${template}");
com.github.jknack.handlebars.Template recipe; com.github.jknack.handlebars.Template recipe;
+37 -2
View File
@@ -13,6 +13,7 @@ import java.io.UnsupportedEncodingException;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@@ -84,7 +85,7 @@ public final class Handy {
*/ */
public static Object normalize(Class<?> clazz, Object val ) { public static Object normalize(Class<?> clazz, Object val ) {
if(val==null) return null; // we are null if(val==null) return null; // we are null
if(clazz.isAssignableFrom(val.getClass())) return clazz; // we are assignable if(clazz.isAssignableFrom(val.getClass())) return val; // we are assignable
if(val instanceof String){ if(val instanceof String){
String value=(String) val; String value=(String) val;
if(value.isEmpty() || value.equals("''") || value.equals("\"\"")) return null; if(value.isEmpty() || value.equals("''") || value.equals("\"\"")) return null;
@@ -96,7 +97,6 @@ public final class Handy {
if( Float.class==( clazz ) || float.class==( clazz ) ) return Float.parseFloat( value ); if( Float.class==( clazz ) || float.class==( clazz ) ) return Float.parseFloat( value );
if( Double.class==( clazz ) || double.class==( clazz )) return Double.parseDouble( value ); if( Double.class==( clazz ) || double.class==( clazz )) return Double.parseDouble( value );
} }
return val; return val;
} }
/** /**
@@ -523,4 +523,39 @@ public final class Handy {
} }
return buf.toString(); return buf.toString();
} }
/** splitting without using regex.
* leading or trailing delims will produce empty tokens.
* if you need to split on a set of single chars please use tokenizer.
* @param delim delim string
* @param str body of text to chop
* @param delim_count maximal number of splits or -1 for all
* @return array of tokens
*/
public static String[] split(String delim,String str,int delim_count) {
ArrayList<String> ret=new ArrayList<>();
int delimLen=delim!=null?delim.length():0;
int len=str.length();
int index=0;
int delimCnt=0; // track splits
int delimAt=0; // last delim position
while(index<len){
if(delim_count>0 && delimCnt>=delim_count) break; // reached limit of delims
delimAt=delimLen>0?str.indexOf(delim, index):index+1;
if(delimAt<0) break; // no more delims
// we got a hit
delimCnt+=1;
ret.add(str.substring(index, delimAt)); // add token
index=delimAt+delimLen;
}
if(index<len || delimAt>0){
// add remainder (no more delim or delim at end)
ret.add(str.substring(index));
}
return ret.toArray(new String[ret.size()]);
}
/** split a string as often as possible. */
public static String[] split(String delim,String str) {
return split(delim,str,-1);
}
} }
+69
View File
@@ -0,0 +1,69 @@
package com.reliancy.util;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
/** Logging support based on JUL.
* We implement a deferred logmanager that survives shutdownhook until we release.
*/
public class Log {
static {
// must be called before any Logger method is used.
System.setProperty("java.util.logging.manager", DeferredMgr.class.getName());
System.setProperty("java.util.logging.SimpleFormatter.format","%1$tF %1$tT %4$-7s [%3$s] %5$s%6$s%n");
}
public static class DeferredMgr extends LogManager {
@Override public void reset() { /* don't reset yet. */ }
private void resetFinally() { super.reset(); }
}
public static Logger setup(){
Logger root_logger=Logger.getLogger("");
return root_logger;
}
public static void cleanup(){
LogManager mgr=LogManager.getLogManager();
if(mgr instanceof DeferredMgr){
((DeferredMgr)mgr).resetFinally();
}
}
public static void setLevel(Logger logger,String level_name){
if(level_name==null || level_name.isEmpty()) level_name="ERROR";
level_name=level_name.toUpperCase();
switch(level_name){
case "v":{
level_name="WARN";
break;
}
case "vv":{
level_name="INFO";
break;
}
case "vvv":{
level_name="DEBUG";
break;
}
}
switch(level_name){
case "WARN":{
level_name="WARNING";
break;
}
case "DEBUG":{
level_name="FINER";
break;
}
case "ERROR":{
level_name="SEVERE";
break;
}
}
Level lvl=Level.parse(level_name);
logger.setLevel(lvl);
for (Handler h : logger.getHandlers()) {
h.setLevel(lvl);
}
}
}
+29 -10
View File
@@ -8,6 +8,7 @@ You may not use this file except in compliance with the License.
package com.reliancy.util; package com.reliancy.util;
import java.io.File; import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLDecoder; import java.net.URLDecoder;
@@ -42,8 +43,15 @@ public class Path {
String database; ///< name of database or filename String database; ///< name of database or filename
String properties; ///< properties are what follows ? in a url String properties; ///< properties are what follows ? in a url
public Path(String connect,boolean do_parse) {
if(do_parse){
parse(connect);
}else{
connectstring=connect;
}
}
public Path(String connect) { public Path(String connect) {
setConnectString(connect); this(connect,true);
} }
public Path(Path in) { public Path(Path in) {
@@ -59,7 +67,7 @@ public class Path {
@Override @Override
public String toString() { public String toString() {
return getConnectString(); return assemble();
} }
/** /**
* Converts the path to a file. * Converts the path to a file.
@@ -85,7 +93,7 @@ public class Path {
if(Handy.isBlank(getHost()) && path.contains("://") && !path.contains(":///")) path=path.replace("://",":///"); if(Handy.isBlank(getHost()) && path.contains("://") && !path.contains(":///")) path=path.replace("://",":///");
return new URL(path); return new URL(path);
} }
public void clear(){ public Path clear(){
connectstring=null; connectstring=null;
protocol=null; protocol=null;
userid=null; userid=null;
@@ -94,9 +102,9 @@ public class Path {
port=null; port=null;
database=null; database=null;
properties=null; properties=null;
return this;
} }
public String assemble() {
public String getConnectString() {
if(connectstring!=null) return connectstring; if(connectstring!=null) return connectstring;
// assemble the connect string // assemble the connect string
StringBuilder buf=new StringBuilder(); StringBuilder buf=new StringBuilder();
@@ -131,10 +139,10 @@ public class Path {
return connectstring; return connectstring;
} }
public void setConnectString(String connect) { public Path parse(String connect) {
clear(); clear();
if (connect == null) { if (connect == null) {
return; return this;
} }
this.connectstring=connect; this.connectstring=connect;
// first get protocol - everything up to : which is not followed by a symbol (includes :// but also c:/ // first get protocol - everything up to : which is not followed by a symbol (includes :// but also c:/
@@ -204,6 +212,7 @@ public class Path {
properties = database.substring(st); properties = database.substring(st);
database = database.substring(0, st); database = database.substring(0, st);
} }
return this;
} }
/** /**
@@ -411,12 +420,22 @@ public class Path {
return str.split("&"); return str.split("&");
} }
public static String[] splitKeyValue(String str) { public static String[] splitKeyValue(String str) {
String[] t=str.split("="); String[] t=Handy.split("=",str,1);
if(t==null || t.length==0) return null; if(t==null || t.length==0) return null;
t[0]=Handy.trim(t[0],"'\""); t[0]=Handy.trim(t[0],"'\"");
return t; try {
t[1]=URLDecoder.decode(t[1],"UTF-8");
t[1]=Handy.trim(t[1],"'\"");
return t;
} catch (Exception e) {
if(t.length<2){
return new String[]{t[0],null};
}else{
return t;
}
}
} }
public static String[] split(String str) { public static String[] split(String str) {
return str.split("/"); return Handy.split("/",str);
} }
} }
+44 -6
View File
@@ -19,18 +19,47 @@ import java.io.OutputStreamWriter;
import java.io.Reader; import java.io.Reader;
import java.io.Writer; import java.io.Writer;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.JarURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
/** Static utility with helper methods to read or write resources.
* The place where we host a global search path often used by others
* such as Template or FileServe (unless overriden.)
*/
public class Resources { public class Resources {
public static Object[] search_path;
/** appends one+ paths to search at position pos.
* neg pos substracts from end
*/
public static Object[] appendSearch(int pos,Object ...src){
if(search_path==null) search_path=new Object[0];
if(pos<0) pos=search_path.length+pos+1;
if(pos<0 || pos>search_path.length) throw new IndexOutOfBoundsException("at:"+pos);
Object[] new_path=new Object[search_path.length+src.length];
// first left side of old search path
if(pos>0){
System.arraycopy(search_path, 0, new_path, 0, pos);
}
// next new sources
if(src.length>0){
System.arraycopy(src, 0, new_path, pos, src.length);
}
// lastly right side of old search path
System.arraycopy(search_path,pos, new_path,pos+src.length, search_path.length-pos);
search_path=new_path;
return search_path;
}
public static interface PathRewrite{ public static interface PathRewrite{
public String rewritePath(String path,Object context); public String rewritePath(String path,Object context);
} }
public static URL findFirst(PathRewrite remap,String path,Object ... sp){ public static URL findFirst(PathRewrite remap,String path,Object ... sp){
String path0=path;
if(sp==null || sp.length==0) sp=search_path;
for(Object base:sp){ for(Object base:sp){
if(remap!=null) path=remap.rewritePath(path,base); if(remap!=null) path=remap.rewritePath(path0,base);
if(base instanceof Class){ if(base instanceof Class){
URL ret=((Class<?>)base).getResource(path); URL ret=((Class<?>)base).getResource(path);
return ret; return ret;
@@ -57,11 +86,20 @@ public class Resources {
URL ret=new URL((URL)base,path); URL ret=new URL((URL)base,path);
String proto=ret.getProtocol(); String proto=ret.getProtocol();
if(proto.equals("http") || proto.equals("https")){ if(proto.equals("http") || proto.equals("https")){
HttpURLConnection huc = (HttpURLConnection) ret.openConnection(); HttpURLConnection huc = null;
huc.setRequestMethod("HEAD"); try{
int responseCode = huc.getResponseCode(); huc=(HttpURLConnection) ret.openConnection();
huc.disconnect(); huc.setRequestMethod("HEAD");
if(responseCode==HttpURLConnection.HTTP_OK) return ret; int responseCode = huc.getResponseCode();
if(responseCode==HttpURLConnection.HTTP_OK) return ret;
}finally{
if(huc!=null) huc.disconnect();
}
}
if(proto.startsWith("jar")){
JarURLConnection juc = null;
juc=(JarURLConnection) ret.openConnection();
if(juc.getJarEntry()!=null) return ret;
} }
if(proto.equals("file")){ if(proto.equals("file")){
File f=new File(ret.getPath()); File f=new File(ret.getPath());
+14
View File
@@ -0,0 +1,14 @@
html, body {
height: 100%;
}
.fa-2x {
vertical-align: middle;
}
.col-fixed {
margin: 1em auto;
}
@media (min-width: 768px) {
.col-fixed {
width: 600px;
}
}
+29
View File
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<html>
<head>
<meta charset="utf-8">
<title>{{app_title}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.0/css/all.css">
<link rel="stylesheet" href="/static/theme.css">
{{#block "MetaEx"}}
{{/block}}
</head>
<body>
{{#block "Header"}}
{{/block}}
{{#block "Detail"}}
{{/block}}
{{#block "Footer"}}
{{/block}}
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<!-- Popper JS -->
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
<!-- Latest compiled JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
@@ -2,7 +2,9 @@
<div class="row justify-content-center mt-3"> <div class="row justify-content-center mt-3">
<div class="card"> <div class="card">
<div class="card-header alert alert-danger"> <div class="card-header alert alert-danger">
<h5 class="card-title"><i class="fas fa-exclamation-circle fa-2x"></i> {{error_title}}</h5> <h5 class="card-title">
<i class="fas fa-exclamation-circle fa-2x"></i> {{error_title}}
</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<!--<h5 class="card-title">Special title treatment</h5>--> <!--<h5 class="card-title">Special title treatment</h5>-->
@@ -15,4 +17,4 @@
</div> </div>
{{/partial}} {{/partial}}
{{> frame-1c}} {{> __frame__}}
+79
View File
@@ -0,0 +1,79 @@
{{#partial "Header"}}
<!-- Responsive navbar-->
<nav class="navbar navbar-expand-md navbar-light bg-light">
<div class="container-fluid">
<!-- Brand -->
<div class="navbar-header">
<a class="navbar-brand" href="#">
{{#with menu}}
{{title}}
{{/with}}
</a>
</div>
<!-- Toggler/collapsibe Button -->
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
<span class="navbar-toggler-icon"></span>
</button>
<!-- Links -->
<div class="collapse navbar-collapse" id="collapsibleNavbar">
{{#with menu}}
<ul class="navbar-nav mr-auto">
{{#each before}}
<li class="nav-item">
<a class="nav-link" href="{{url}}">{{title}}</a>
</li>
{{/each}}
</ul>
<ul class="navbar-nav ml-auto">
{{#each after}}
<li class="nav-item">
<a class="nav-link" href="{{url}}">{{title}}</a>
</li>
{{/each}}
</ul>
{{/with}}
<!-- Dropdown -->
<!--
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbardrop" data-toggle="dropdown">
Dropdown link
</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Link 1</a>
<a class="dropdown-item" href="#">Link 2</a>
<a class="dropdown-item" href="#">Link 3</a>
</div>
</li>
-->
</div>
</div>
</nav>
<!--this is the feedback messages-->
<div id="MessageSection" class="row">
{{#each feedback}}
<div class="col-md-12 alert-{{type}}">{{message}}</div>
{{/each}}
<!--
<div class="col-sm-12 alert alert-danger">This is a message</div>
-->
</div>
{{/partial}}
{{#partial "Detail"}}
<div class="container">
<div class="row justify-content-md-center">
<div class="col-2" id="LeftSection">
{{#block "LeftSection"}}
Left Section
{{/block}}
</div>
<div class="col-10" id="MainSection">
{{#block "MainSection"}}
Main Section
{{/block}}
</div>
</div>
{{/partial}}
{{> base}}
+71
View File
@@ -0,0 +1,71 @@
{{#partial "Header"}}
<!-- Responsive navbar-->
<nav class="navbar navbar-expand-md navbar-light bg-light">
<div class="container-fluid">
<!-- Brand -->
<div class="navbar-header">
<a class="navbar-brand" href="#">
{{#with menu}}
{{title}}
{{/with}}
</a>
</div>
<!-- Toggler/collapsibe Button -->
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
<span class="navbar-toggler-icon"></span>
</button>
<!-- Links -->
<div class="collapse navbar-collapse" id="collapsibleNavbar">
{{#with menu}}
<ul class="navbar-nav mr-auto">
{{#each before}}
<li class="nav-item">
<a class="nav-link" href="{{url}}">{{title}}</a>
</li>
{{/each}}
</ul>
<ul class="navbar-nav ml-auto">
{{#each after}}
<li class="nav-item">
<a class="nav-link" href="{{url}}">{{title}}</a>
</li>
{{/each}}
</ul>
{{/with}}
<!-- Dropdown -->
<!--
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbardrop" data-toggle="dropdown">
Dropdown link
</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Link 1</a>
<a class="dropdown-item" href="#">Link 2</a>
<a class="dropdown-item" href="#">Link 3</a>
</div>
</li>
-->
</div>
</div>
</nav>
<!--this is the feedback messages-->
<div id="MessageSection" class="row">
{{#each feedback}}
<div class="col-md-12 alert-{{type}}">{{message}}</div>
{{/each}}
<!--
<div class="col-sm-12 alert alert-danger">This is a message</div>
-->
</div>
{{/partial}}
{{#partial "Detail"}}
<div id="MainSection" class="container">
{{#block "MainSection"}}
Main Section
{{/block}}
</div>
{{/partial}}
{{> base}}
@@ -21,4 +21,4 @@
</form> </form>
</div> </div>
{{/partial}} {{/partial}}
{{> frame-1c}} {{> __frame__}}
@@ -60,7 +60,7 @@ public class TerminalTest {
@BeforeClass @BeforeClass
public static void beforeAllTestMethods() { public static void beforeAllTestMethods() {
System.out.println("Invoked once before all test methods"); System.out.println("Invoked once before all test methods");
String url="jdbc:postgresql://postgres:Ramudin99@bigbang:5432/Test"; String url=System.getenv("DB_URL");
t=new SQLTerminal(url); t=new SQLTerminal(url);
} }
@@ -0,0 +1,34 @@
package com.reliancy.jabba;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import org.junit.Test;
public class ArgsConfigTest {
@Test
public void testEnv(){
System.out.println("testing sys env...");
ArgsConfig args=new ArgsConfig("prog","--verbose","--key","value","cmd");
try {
args.load();
ArgsConfig.Property<String> env_user=new ArgsConfig.Property<>("USER",String.class);
ArgsConfig.Property<String> sys_user=new ArgsConfig.Property<>("user.name",String.class);
ArgsConfig.Property<Boolean> verbose=new ArgsConfig.Property<>("verbose",Boolean.class);
String usr_val1=args.getProperty(env_user,"None1");
String usr_val2=args.getProperty(sys_user,"None2");
System.out.println("Env User:"+usr_val1);
System.out.println("Sys User:"+usr_val2);
assertTrue(usr_val1.equals(usr_val2));
System.out.println("Positional:"+args.getProperty(Config.APP_ARGS, null));
System.out.println("Verbose:"+verbose.get(args));
for(ArgsConfig.Property<?> p:args){
System.out.println("p:"+p+"="+p.get(args));
}
} catch (IOException e) {
e.printStackTrace();
assertTrue(false);
}
}
}
@@ -39,7 +39,7 @@ public class RouterTest
//assertTrue( true ); //assertTrue( true );
System.out.println("Test router init..."); System.out.println("Test router init...");
JettyApp r=new JettyApp(); JettyApp r=new JettyApp();
RoutedEndPoint rep=new RoutedEndPoint(); Router rep=new Router();
rep.importMethods(r); rep.importMethods(r);
rep.compile(); rep.compile();
//Matcher m=rep.match("GET","/helloPlain"); //Matcher m=rep.match("GET","/helloPlain");
@@ -0,0 +1,40 @@
/*
Copyright (c) 2011-2022 Reliancy LLC
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
You may not use this file except in compliance with the License.
*/
package com.reliancy.util;
import java.io.IOException;
import org.junit.Test;
public class HandyTest {
/**
* Plain CRUD
* @throws IOException
*/
@Test
public void splitting() throws IOException
{
System.out.println("Splitting test...");
String tst1="One,Two,Three";
System.out.println(tst1+" over ,");
for(String s:Handy.split(",",tst1)){
System.out.println("\tt:"+s);
}
//System.out.println(tst1+" over ");
//for(String s:Handy.split("",tst1)){
// System.out.println("\tt:"+s);
//}
String tst2="AND A AND B ANDAND D AND";
System.out.println(tst2+" over AND");
for(String s:Handy.split("AND",tst2)){
System.out.println("\tt:"+s);
}
}
}