fixed shutdown sequence, added better modularity
This commit is contained in:
+6
-6
@@ -1,11 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<attributes>
|
||||
<attribute name="gradle_scope" value="test"/>
|
||||
@@ -19,6 +13,12 @@
|
||||
<attribute name="gradle_used_by_scope" value="main,test"/>
|
||||
</attributes>
|
||||
</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.buildship.core.gradleclasspathcontainer"/>
|
||||
<classpathentry kind="output" path="target/bin"/>
|
||||
|
||||
@@ -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
|
||||
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=
|
||||
eclipse.preferences.version=1
|
||||
gradle.user.home=
|
||||
java.home=C\:/Program Files/Microsoft/jdk-11.0.12.7-hotspot
|
||||
java.home=
|
||||
jvm.arguments=
|
||||
offline.mode=false
|
||||
override.workspace.settings=true
|
||||
show.console.view=true
|
||||
show.executions.view=true
|
||||
override.workspace.settings=false
|
||||
show.console.view=false
|
||||
show.executions.view=false
|
||||
|
||||
@@ -10,7 +10,7 @@ org.eclipse.jdt.core.circularClasspath=warning
|
||||
org.eclipse.jdt.core.classpath.exclusionPatterns=enabled
|
||||
org.eclipse.jdt.core.classpath.mainOnlyProjectHasTestOnlyDependency=error
|
||||
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.argumentSuffixes=
|
||||
org.eclipse.jdt.core.codeComplete.camelCaseMatch=enabled
|
||||
|
||||
+41
-15
@@ -4,26 +4,50 @@ Unix - ~/.m2
|
||||
Windows - C:\Users\<username>\.m2
|
||||
For example - /Users/alex/.m2/repository/<library_path>/<version>/<name>.<extension>
|
||||
*/
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'application'
|
||||
apply plugin: 'eclipse'
|
||||
apply from: 'extra.gradle'
|
||||
|
||||
group='com.reliancy'
|
||||
mainClassName = group+'.'+name+'.JettyApp'
|
||||
version = '0.2-SNAPSHOT'
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
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
|
||||
println("group:"+group);
|
||||
println("name:"+name);
|
||||
println("version:"+version);
|
||||
println("entry:"+mainClassName);
|
||||
//println("group:"+group);
|
||||
//println("name:"+name);
|
||||
//println("version:"+version);
|
||||
//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 {
|
||||
//mavenLocal()
|
||||
//mavenCentral()
|
||||
@@ -75,7 +99,8 @@ javadoc {
|
||||
}
|
||||
dependencies {
|
||||
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.github.jknack:handlebars:4.3.0'
|
||||
implementation 'com.h2database:h2:2.1.214'
|
||||
@@ -92,9 +117,10 @@ test {
|
||||
// standard out or error is shown
|
||||
// in Gradle output.
|
||||
outputs.upToDateWhen {false}
|
||||
showStandardStreams = true
|
||||
//showStandardStreams = true
|
||||
exceptionFormat = 'full'
|
||||
// Or we use events method:
|
||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||
// events 'standard_out', 'standard_error'
|
||||
|
||||
// Or set property events:
|
||||
@@ -110,7 +136,7 @@ jar {
|
||||
archiveBaseName = project.name
|
||||
archiveVersion = project.version
|
||||
manifest {
|
||||
attributes "Main-Class": mainClassName
|
||||
attributes "Main-Class": application.mainClass
|
||||
attributes "Class-Path": configurations.runtimeClasspath.collect { it.getName() }.join(' ')
|
||||
}
|
||||
}
|
||||
@@ -121,11 +147,11 @@ task copyToLib(type: Copy) {
|
||||
build.finalizedBy(copyToLib)
|
||||
eclipse{
|
||||
classpath {
|
||||
defaultOutputDir = file("target/bin") ///default
|
||||
defaultOutputDir = file("${relativePath(buildDir)}/bin") ///default
|
||||
file.whenMerged { cp ->
|
||||
cp.entries.forEach { cpe ->
|
||||
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
@@ -12,11 +12,11 @@ task dotenv{
|
||||
}
|
||||
task packageJavadoc(type: Jar, dependsOn: 'javadoc') {
|
||||
from javadoc
|
||||
classifier = 'javadoc'
|
||||
archiveClassifier = 'javadoc'
|
||||
}
|
||||
task packageSources(type: Jar, dependsOn: 'classes') {
|
||||
from sourceSets.main.allSource
|
||||
classifier = 'sources'
|
||||
archiveClassifier = 'sources'
|
||||
}
|
||||
task fat_jar(type: Jar) {
|
||||
archiveBaseName = 'fat-'+project.name
|
||||
@@ -92,7 +92,7 @@ class Server implements Runnable{
|
||||
info("stopping server");
|
||||
if(driver!=null){
|
||||
driver.interrupt();
|
||||
driver.join();
|
||||
//driver.join();
|
||||
}
|
||||
for(Thread th:Thread.getAllStackTraces().keySet()){
|
||||
if(th.getName().equalsIgnoreCase("executor")){
|
||||
@@ -116,8 +116,8 @@ task runServer{
|
||||
doLast {
|
||||
Server.main().start({
|
||||
project.javaexec {
|
||||
classpath = project.sourceSets.main.runtimeClasspath
|
||||
main = mainClassName
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
main = application.mainClass.get()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import java.util.Iterator;
|
||||
* At its core are action traits which are classes that define either loading,saving or deleting.
|
||||
* The items field is a consumable object when consumed the action is done.
|
||||
* So for loading we iterate once done it cannot be done again. Also when items are provided for saving
|
||||
* once iterated over and saved they we done.
|
||||
* once iterated over and saved we are done.
|
||||
*/
|
||||
public class Action implements Iterable<DBO>,SiphonIterator<DBO>{
|
||||
public static class Trait{
|
||||
|
||||
@@ -22,6 +22,10 @@ import com.reliancy.util.Path;
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
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{
|
||||
HikariConfig config = new HikariConfig();
|
||||
HikariDataSource ds;
|
||||
@@ -48,7 +52,7 @@ public class SQLTerminal implements Terminal{
|
||||
}
|
||||
@Override
|
||||
public Action execute(Action q) throws IOException{
|
||||
System.out.println("Executing..."+q.getTrait());
|
||||
// System.out.println("Executing..."+q.getTrait());
|
||||
Action.Trait tr=q.getTrait();
|
||||
if(tr instanceof Action.Load){
|
||||
Entity ent=q.getEntity();
|
||||
@@ -60,14 +64,14 @@ public class SQLTerminal implements Terminal{
|
||||
reader.close();
|
||||
throw new IOException(e);
|
||||
}
|
||||
System.out.println("Executing...Done");
|
||||
//System.out.println("Executing...Done");
|
||||
return q;
|
||||
}else if(tr instanceof Action.Save){
|
||||
Entity ent=q.getEntity();
|
||||
try(SQLWriter writer=new SQLWriter(ent,this)) {
|
||||
writer.open();
|
||||
writer.flush(q.getItems());
|
||||
System.out.println("Executing...Done");
|
||||
//System.out.println("Executing...Done");
|
||||
return q;
|
||||
}catch(SQLException e){
|
||||
throw new IOException(e);
|
||||
@@ -77,7 +81,7 @@ public class SQLTerminal implements Terminal{
|
||||
try(SQLCleaner cleaner=new SQLCleaner(ent,this)) {
|
||||
cleaner.open();
|
||||
cleaner.flush(q.getItems());
|
||||
System.out.println("Executing...Done");
|
||||
//System.out.println("Executing...Done");
|
||||
return q;
|
||||
}catch(SQLException e){
|
||||
throw new IOException(e);
|
||||
|
||||
@@ -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
|
||||
* 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.
|
||||
*/
|
||||
public interface Terminal {
|
||||
|
||||
@@ -8,47 +8,81 @@ You may not use this file except in compliance with the License.
|
||||
|
||||
package com.reliancy.jabba;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.reliancy.dbo.Terminal;
|
||||
import com.reliancy.jabba.sec.SecurityPolicy;
|
||||
import com.reliancy.util.CodeException;
|
||||
import com.reliancy.util.ResultCode;
|
||||
|
||||
/** Base Application class from where specific launchers derive.
|
||||
* 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 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}");
|
||||
protected Processor first=null;
|
||||
protected Processor last=null;
|
||||
protected RoutedEndPoint router=null;
|
||||
protected Router router=null;
|
||||
protected SecurityPolicy policy=null;
|
||||
protected Terminal storage=null;
|
||||
|
||||
public App(String id) {
|
||||
super(id);
|
||||
}
|
||||
/** does nothing. */
|
||||
public void before(Request request,Response response) throws IOException{
|
||||
}
|
||||
/** does nothing. */
|
||||
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{
|
||||
if(first!=null) first.process(req, resp);
|
||||
if(router!=null) router.process(req,resp);
|
||||
if(first!=null && resp.getStatus()==null) first.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){
|
||||
last=first=m;
|
||||
m.setParent(m);
|
||||
}else{
|
||||
last.next=m;
|
||||
}
|
||||
while(last.next!=null) last=last.next;
|
||||
while(last.next!=null){
|
||||
last=last.next;
|
||||
last.setParent(m);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
public void removeProcessor(Processor m){
|
||||
public void removeMiddleWare(Processor m){
|
||||
if(m==null) return;
|
||||
if(first==m){
|
||||
if(first==last) last=null;
|
||||
first=first.next;
|
||||
if(first==last){
|
||||
first=last=null;
|
||||
}else{
|
||||
first=first.next;
|
||||
}
|
||||
while(last!=null && last.next!=null) last=last.next;
|
||||
}else{
|
||||
for(Processor prev=first;prev!=null;prev=prev.next){
|
||||
@@ -60,6 +94,7 @@ public abstract class App extends Processor{
|
||||
}
|
||||
}
|
||||
m.next=null;
|
||||
m.setParent(null);
|
||||
}
|
||||
public Processor getProcessor(String id){
|
||||
for(Processor c=first;c!=null;c=c.next){
|
||||
@@ -68,11 +103,26 @@ public abstract class App extends Processor{
|
||||
return null;
|
||||
}
|
||||
|
||||
public RoutedEndPoint getRouter() {
|
||||
public Router getRouter() {
|
||||
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;
|
||||
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 {
|
||||
try{
|
||||
@@ -88,34 +138,41 @@ public abstract class App extends Processor{
|
||||
if(conf==null) throw new CodeException(ERR_NOCONFIG);
|
||||
config=conf;
|
||||
for(Processor p=first;p!=null;p=p.getNext()){
|
||||
p.begin(config);
|
||||
p.begin();
|
||||
}
|
||||
if(router!=null) router.begin(config);
|
||||
}
|
||||
@Override
|
||||
public void end() throws Exception{
|
||||
if(router!=null) router.end();
|
||||
for(Processor p=first;p!=null;p=p.getNext()){
|
||||
p.end();
|
||||
try{
|
||||
if(router!=null) router.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(){
|
||||
return addProcessor(new AppSessionFilter(this));
|
||||
return addMiddleWare(new AppSessionFilter(this));
|
||||
}
|
||||
public AppSessionFilter addAppSession(AppSession.Factory f){
|
||||
return addProcessor(new AppSessionFilter(this,f));
|
||||
return addMiddleWare(new AppSessionFilter(this,f));
|
||||
}
|
||||
public SecurityPolicy setSecurityPolicy(SecurityPolicy secpol){
|
||||
if(secpol==policy) return secpol;
|
||||
if(policy!=null){
|
||||
MethodDecorator.retract(policy);
|
||||
removeProcessor(policy);
|
||||
removeMiddleWare(policy);
|
||||
}
|
||||
policy=secpol;
|
||||
if(policy!=null){
|
||||
addProcessor(policy);
|
||||
addMiddleWare(policy);
|
||||
MethodDecorator.publish(policy); // register security policy as decorator factory
|
||||
}
|
||||
return secpol;
|
||||
@@ -123,4 +180,10 @@ public abstract class App extends Processor{
|
||||
public SecurityPolicy getSecurityPolicy(){
|
||||
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.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 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
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;
|
||||
|
||||
public interface Config {
|
||||
import com.reliancy.util.Handy;
|
||||
|
||||
public interface Config extends Iterable<Config.Property<?>>{
|
||||
public static class Property<V> {
|
||||
|
||||
final String name;
|
||||
final Class<V> typ;
|
||||
V initial;
|
||||
boolean required;
|
||||
boolean writable;
|
||||
public Property(String name,Class<V> typ){
|
||||
this.name=name;
|
||||
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 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){
|
||||
return store.getProperty(this,def);
|
||||
}
|
||||
public V get(Config store){
|
||||
return get(store,null);
|
||||
return get(store,initial);
|
||||
}
|
||||
public void set(Config store,V 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 void save();
|
||||
public static final Property<String> LOG_LEVEL=new Property<>("LOG_LEVEL",String.class);
|
||||
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 <T> boolean hasProperty(Property<T> key);
|
||||
public <T> Config setProperty(Property<T> key,T val);
|
||||
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;
|
||||
|
||||
/** EndPoint is a special processor usually the last in chain.
|
||||
*
|
||||
*/
|
||||
public abstract class EndPoint extends Processor{
|
||||
|
||||
public EndPoint(String id) {
|
||||
|
||||
@@ -7,46 +7,91 @@ You may not use this file except in compliance with the License.
|
||||
*/
|
||||
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;
|
||||
final HashMap<String,Object> props=new HashMap<>();
|
||||
public FileConfig(String p){
|
||||
public FileConfig(Config parent,String p){
|
||||
this.parent=parent;
|
||||
path=p;
|
||||
load();
|
||||
}
|
||||
public FileConfig(){
|
||||
this(null);
|
||||
public FileConfig(String p){
|
||||
this(null,p);
|
||||
}
|
||||
public void clear(){
|
||||
public Config clear(){
|
||||
props.clear();
|
||||
return this;
|
||||
}
|
||||
@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
|
||||
public void save() {
|
||||
public Config save() throws IOException{
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setId(String path) {
|
||||
public FileConfig setId(String path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
@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) {
|
||||
if(props.containsKey(key.getName())) return key.getTyp().cast(props.get(key.getName()));
|
||||
else return def;
|
||||
if(parent!=null && parent.hasProperty(key)){
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,70 +10,90 @@ import com.reliancy.util.Resources;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.stream.Stream;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class FileServer extends EndPoint implements Resources.PathRewrite{
|
||||
public static interface Filter{
|
||||
boolean isAcceptable(String path);
|
||||
/** FileServer is an module and endpoint that exposes multiple URLs thru which files are served.
|
||||
* First it will be just get(tting), later
|
||||
* 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{
|
||||
final String[] allowed;
|
||||
public ExtFilter(String ...ext){allowed=ext;}
|
||||
public static class FileBucket implements Bucket{
|
||||
final String prefix;
|
||||
String[] extAllowed;
|
||||
Object[] domain;
|
||||
public FileBucket(String prefix){
|
||||
this.prefix=prefix;
|
||||
extAllowed=new String[]{};
|
||||
domain=new Object[]{};
|
||||
}
|
||||
@Override
|
||||
public boolean isAcceptable(String path) {
|
||||
if(allowed.length==0) return true;
|
||||
for(String ext:allowed) if(path.endsWith(ext)) return true;
|
||||
public final String getPrefix(){return prefix;}
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
}
|
||||
public static Filter NOFILTER=new ExtFilter();
|
||||
String diskPrefix;
|
||||
String classPrefix;
|
||||
final HashMap<String,Object[]> map;
|
||||
final HashMap<String,Filter> filt;
|
||||
public FileServer(String url_path,Filter f,Object ... disk_path){
|
||||
super("fileserver");
|
||||
diskPrefix=classPrefix=null;
|
||||
filt=new HashMap<>();
|
||||
map=new HashMap<>();
|
||||
addRoute(url_path,f, disk_path);
|
||||
}
|
||||
public FileServer(String url_path,Object ... disk_path){
|
||||
this(url_path,NOFILTER,disk_path);
|
||||
}
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
@Override
|
||||
public boolean equals(String pref){return prefix.equals(pref);}
|
||||
public FileBucket setDomain(Object...sp){
|
||||
domain=sp;
|
||||
return this;
|
||||
}
|
||||
public Object[] getDomain(){
|
||||
return (domain!=null && domain.length>0)?domain:Resources.search_path;
|
||||
}
|
||||
public InputStream openSource(String local_path,FileServer user) throws IOException{
|
||||
Object[] sp=getDomain();
|
||||
URL f=Resources.findFirst(user,local_path, sp);
|
||||
if(f==null) return null; // skip if rpath not located
|
||||
return f.openStream();
|
||||
}
|
||||
public OutputStream openSink(String local_path,FileServer user) throws IOException{
|
||||
return null;
|
||||
}
|
||||
response.setStatus(Response.HTTP_NOT_FOUND);
|
||||
response.getEncoder().writeln("missing file:{0}",path);
|
||||
this.log().error("not found:"+path);
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
return path;
|
||||
final ArrayList<Bucket> buckets=new ArrayList<>();
|
||||
String diskPrefix; // will be prefixed to source if file
|
||||
String classPrefix; // will be prefixed to source if class
|
||||
String urlPrefix; // will be prefixed to source if URL
|
||||
public FileServer(String url_path,String offset,Object ... source){
|
||||
super(null);
|
||||
diskPrefix=classPrefix=offset;
|
||||
addBucket(new FileBucket(url_path).setDomain(source));
|
||||
}
|
||||
public FileServer(){
|
||||
super(null);
|
||||
}
|
||||
public FileServer setDiskPrefix(String prefix){
|
||||
diskPrefix=prefix;
|
||||
@@ -83,12 +103,50 @@ public class FileServer extends EndPoint implements Resources.PathRewrite{
|
||||
classPrefix=prefix;
|
||||
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 response
|
||||
*/
|
||||
protected void writeResource(URL f, Response response) throws IOException{
|
||||
protected static void writeResource(URL f, Response response) throws IOException{
|
||||
//log().info("writing:"+f);
|
||||
ResponseEncoder enc=response.getEncoder();
|
||||
try(InputStream is=f.openStream()){
|
||||
@@ -98,28 +156,29 @@ public class FileServer extends EndPoint implements Resources.PathRewrite{
|
||||
enc.writeStream(is);
|
||||
}
|
||||
}
|
||||
public final void addRoute(String url_path,Filter f,Object... disk_path){
|
||||
if(disk_path!=null){
|
||||
map.put(url_path,disk_path);
|
||||
filt.put(url_path,f!=null?f:NOFILTER);
|
||||
}else{
|
||||
map.remove(url_path);
|
||||
filt.remove(url_path);
|
||||
}
|
||||
/** adds a route which serves files.
|
||||
* if disk_path is ommited (0 len) or null we use Resources.search_path.
|
||||
* @param url_path
|
||||
* @param f
|
||||
* @param disk_path search path for resource
|
||||
*/
|
||||
public final FileServer addBucket(Bucket bucket){
|
||||
buckets.add(bucket);
|
||||
return this;
|
||||
}
|
||||
public Object[] getSearchPath(String url_path){
|
||||
return map.get(url_path);
|
||||
public FileServer delBucket(Bucket bucket){
|
||||
buckets.remove(bucket);
|
||||
return this;
|
||||
}
|
||||
public Filter getFilter(String url_path){
|
||||
return filt.get(url_path);
|
||||
public Bucket getBucket(String url_path){
|
||||
for(Bucket b:buckets) if(b.equals(url_path)) return b;
|
||||
return null;
|
||||
}
|
||||
public Stream<String> streamRoutes() {
|
||||
return map.keySet().stream();
|
||||
public Iterator<Bucket> enumBuckets(){
|
||||
return buckets.iterator();
|
||||
}
|
||||
public Iterator<String> enumRoutes(){
|
||||
return map.keySet().iterator();
|
||||
}
|
||||
public void exportRoutes(RoutedEndPoint rep) {
|
||||
streamRoutes().forEach(up->rep.addRoute("GET",up+".*",this));
|
||||
public void publish(App app) {
|
||||
Router rep=app.getRouter();
|
||||
for(Bucket b:buckets) rep.addRoute("GET",b.getPrefix()+".*",this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,15 @@ import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
|
||||
/** HTTP related methods and classes. */
|
||||
/** HTTP related methods and classes.
|
||||
*
|
||||
*/
|
||||
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 class Header{
|
||||
public String key;
|
||||
@@ -30,6 +37,11 @@ public final class HTTP {
|
||||
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){
|
||||
if(MIME_MAP.isEmpty()){
|
||||
MIME_MAP.put("ico","image/x-icon");
|
||||
@@ -42,35 +54,44 @@ public final class HTTP {
|
||||
MIME_MAP.put("jpeg","image/jpeg");
|
||||
MIME_MAP.put("png","image/png");
|
||||
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("csv","text/csv");
|
||||
MIME_MAP.put("html","text/html");
|
||||
MIME_MAP.put("htm","text/html");
|
||||
MIME_MAP.put("html",MIME_HTML);
|
||||
MIME_MAP.put("htm",MIME_HTML);
|
||||
MIME_MAP.put("xml","text/xml");
|
||||
MIME_MAP.put("json",MIME_JSON);
|
||||
}
|
||||
ext=ext.substring(ext.lastIndexOf(".")+1).toLowerCase();
|
||||
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) {
|
||||
if(ret instanceof CharSequence){
|
||||
CharSequence retstr=(CharSequence)ret;
|
||||
for(int index=0;index<retstr.length();index++){
|
||||
char ch=retstr.charAt(index);
|
||||
if(Character.isWhitespace(ch)) continue;
|
||||
if(ch=='<') return "text/html";
|
||||
if(ch=='{' || ch=='[') return "application/json";
|
||||
if(ch=='<') return MIME_HTML;
|
||||
if(ch=='{' || ch=='[') return MIME_JSON;
|
||||
break;
|
||||
}
|
||||
return "text/plain";
|
||||
return MIME_PLAIN;
|
||||
}
|
||||
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 ext=path.substring(path.lastIndexOf(".")+1).toLowerCase();
|
||||
String mime=ext2mime(ext);
|
||||
return mime!=null?mime:"application/octet-stream";
|
||||
return mime!=null?mime:MIME_BYTES;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -10,20 +10,15 @@ package com.reliancy.jabba;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
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.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.MenuItem;
|
||||
import com.reliancy.jabba.ui.Rendering;
|
||||
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.Handler;
|
||||
@@ -59,6 +54,14 @@ public class JettyApp extends App implements Handler{
|
||||
_state=State.STOPPED;
|
||||
}
|
||||
/** 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
|
||||
public Server getServer() {
|
||||
return jetty;
|
||||
@@ -66,10 +69,12 @@ public class JettyApp extends App implements Handler{
|
||||
|
||||
@Override
|
||||
public void setServer(Server arg0) {
|
||||
System.out.println("setServer..."+jetty+"/"+arg0);
|
||||
jetty=arg0;
|
||||
}
|
||||
@Override
|
||||
public boolean addEventListener(EventListener arg0) {
|
||||
System.out.println("adding evt listener...");
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
@@ -111,11 +116,28 @@ public class JettyApp extends App implements Handler{
|
||||
@Override
|
||||
public void start() throws Exception {
|
||||
_state=State.STARTED;
|
||||
jetty.setConnectors(getConnectors());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
_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
|
||||
@@ -132,7 +154,6 @@ public class JettyApp extends App implements Handler{
|
||||
HttpServletResponse response)
|
||||
throws IOException
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
com.reliancy.jabba.Request req=new com.reliancy.jabba.Request(request);
|
||||
Response resp=new Response(response);
|
||||
|
||||
@@ -143,40 +164,32 @@ public class JettyApp extends App implements Handler{
|
||||
}catch(IOException ioex){
|
||||
Template t=Template.find("/templates/error.hbs");
|
||||
if(t==null) throw ioex;
|
||||
Rendering.begin(t)
|
||||
.with(ioex)
|
||||
.end(resp.getEncoder().getWriter());
|
||||
Rendering.begin(t).with(ioex).end(resp);
|
||||
log().error("error:",ioex);
|
||||
}catch(RuntimeException rex){
|
||||
Template t=Template.find("/templates/error.hbs");
|
||||
if(t==null) throw rex;
|
||||
Rendering.begin(t)
|
||||
.with(rex)
|
||||
.end(resp.getEncoder().getWriter());
|
||||
Rendering.begin(t).with(rex).end(resp);
|
||||
log().error("error:",rex);
|
||||
}finally{
|
||||
baseRequest.setHandled(true);
|
||||
ss.end();
|
||||
}
|
||||
}
|
||||
/** 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{
|
||||
// step 2: configure application, might add processors, adjust config
|
||||
configure(conf);
|
||||
// step 1: install config then begin by signaling all middleware
|
||||
super.begin(conf);
|
||||
jetty.setConnectors(getConnectors());
|
||||
// step 2: start jetty
|
||||
try{
|
||||
log().info("starting...");
|
||||
jetty.start();
|
||||
}catch(Exception ex){
|
||||
setState(State.FAILED);
|
||||
if(ex.getCause() instanceof java.net.BindException){
|
||||
log().error("Bind issue",ex);
|
||||
log().error("bind issue",ex);
|
||||
Thread.sleep(3000);
|
||||
}else throw ex;
|
||||
}
|
||||
@@ -186,150 +199,61 @@ public class JettyApp extends App implements Handler{
|
||||
if(jetty!=null) jetty.join();
|
||||
}
|
||||
public void end() throws Exception{
|
||||
//setState(State.STOPPING);
|
||||
super.end();
|
||||
Connector[] connectors=jetty.getConnectors();
|
||||
// System.out.println(connectors);
|
||||
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...");
|
||||
Log.cleanup(); // release logging in case we deferred
|
||||
System.gc(); // sweep memory just in caser
|
||||
}
|
||||
public static void main( String[] args ) throws Exception{
|
||||
//System.out.println("Hello World!");
|
||||
//String rt=new File(".").getAbsolutePath();
|
||||
//System.out.println("ROOT:"+rt);
|
||||
String work_dir="./var";
|
||||
if(new File(work_dir).exists()==false){
|
||||
work_dir="../var";
|
||||
}
|
||||
Template.search_path(work_dir,App.class);
|
||||
JettyApp app=new JettyApp();
|
||||
/** called from begin just before jetty starts.
|
||||
* this method is called before middleware is notified so we can add or adjust config.
|
||||
* override to hook up your application.
|
||||
* normally follows configuraion and does common sense steps.
|
||||
* might install middleware (processors) which are later passed config.
|
||||
*/
|
||||
public void configure(Config conf) throws Exception{
|
||||
App app=this;
|
||||
// setup global search path - include workdir first, then get class and app.class
|
||||
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();
|
||||
// set security policy
|
||||
SecurityPolicy secpol=new SecurityPolicy().setStore(new PlainSecurityStore());
|
||||
app.setSecurityPolicy(secpol);
|
||||
RoutedEndPoint rep=new RoutedEndPoint().importMethods(app);
|
||||
app.setRouter(rep);
|
||||
FileServer fs=new FileServer("/static",work_dir+"/public");
|
||||
fs.exportRoutes(app.getRouter());
|
||||
// install router
|
||||
app.setRouter(new Router());
|
||||
DemoEP ep=new DemoEP();
|
||||
ep.publish(app);
|
||||
// install file sever endpoint
|
||||
FileServer fs=new FileServer("/static","/public");
|
||||
fs.publish(app);
|
||||
Menu top_menu=Menu.request(Menu.TOP);
|
||||
top_menu.add(new MenuItem("home")).addSpacer().add(new MenuItem("login"));
|
||||
top_menu.setTitle("Jabba");
|
||||
app.run(new FileConfig());
|
||||
//System.out.println("Goodbye World!");
|
||||
top_menu.setTitle("Jabba3");
|
||||
}
|
||||
|
||||
@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
|
||||
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()));
|
||||
public static void main( String[] args ) throws Exception{
|
||||
Config cnf=new ArgsConfig(args).load();
|
||||
JettyApp app=new JettyApp();
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
if(app.isRunning()){
|
||||
try {
|
||||
app.jetty.stop();
|
||||
synchronized(app){
|
||||
app.wait(5000);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
app.log().error("shutdown cleanup:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
//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.getEncoder().getWriter());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}));
|
||||
app.run(cnf);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,12 @@ import java.io.IOException;
|
||||
import org.slf4j.Logger;
|
||||
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 {
|
||||
protected Processor parent;
|
||||
protected Processor next;
|
||||
protected String id;
|
||||
protected boolean active;
|
||||
@@ -32,21 +37,30 @@ public abstract class Processor {
|
||||
public void setNext(Processor 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() {
|
||||
return active;
|
||||
}
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public Config getConfig() {
|
||||
return config;
|
||||
if(config!=null) return config;
|
||||
if(parent!=null) return parent.getConfig();
|
||||
return null;
|
||||
}
|
||||
/*
|
||||
public void setConfig(Config config) {
|
||||
this.config = config;
|
||||
}
|
||||
*/
|
||||
// using config as a marker of a run so set during begin
|
||||
// public void setConfig(Config config) {
|
||||
// this.config = config;
|
||||
// }
|
||||
/**
|
||||
* Main event processing chain.
|
||||
* Will go down the chain until result code is set.
|
||||
@@ -70,9 +84,19 @@ public abstract class Processor {
|
||||
ss.leave(this);
|
||||
}
|
||||
}
|
||||
/** Place to prepare for a run. */
|
||||
public void begin(Config conf) throws Exception{
|
||||
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{
|
||||
this.config=null;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public class Request {
|
||||
return http_request.getMethod();
|
||||
}
|
||||
/**
|
||||
* Look for this parameter in pathParan, queryParams and forms.
|
||||
* Look for this parameter in pathParam, queryParams and forms.
|
||||
* @param pname
|
||||
* @return
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,7 @@ You may not use this file except in compliance with the License.
|
||||
package com.reliancy.jabba;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -15,6 +16,7 @@ import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Collection;
|
||||
@@ -25,22 +27,30 @@ import java.util.Locale;
|
||||
* This class will replace the Java writer.
|
||||
* 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.
|
||||
* implements closeable,autocloseable,appendable.
|
||||
* we do not close
|
||||
*/
|
||||
public class ResponseEncoder {
|
||||
public class ResponseEncoder implements Appendable,Closeable{
|
||||
protected final Response response;
|
||||
protected final Locale locale;
|
||||
//protected final Locale locale;
|
||||
protected Writer writer;
|
||||
protected OutputStream out;
|
||||
|
||||
protected Charset charSet;
|
||||
public ResponseEncoder(Response r){
|
||||
response=r;
|
||||
locale=Locale.getDefault();
|
||||
this(r,StandardCharsets.UTF_8);
|
||||
//response=r;
|
||||
//locale=Locale.getDefault();
|
||||
}
|
||||
public ResponseEncoder(Response r,Locale loc){
|
||||
public ResponseEncoder(Response r,Charset chset){
|
||||
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(response.getStatus()==null) response.setStatus(Response.HTTP_OK);
|
||||
if(response.getContentType()==null) response.setContentType("application/octet-stream");
|
||||
@@ -51,10 +61,10 @@ public class ResponseEncoder {
|
||||
}else{
|
||||
out=new ByteArrayOutputStream();
|
||||
}
|
||||
writer=new OutputStreamWriter(out,StandardCharsets.UTF_8);
|
||||
writer=new OutputStreamWriter(out,charSet);
|
||||
return out;
|
||||
}
|
||||
protected Writer getWriter() throws IOException{
|
||||
public Writer getWriter() throws IOException{
|
||||
if(writer!=null) return writer;
|
||||
if(response.getStatus()==null) response.setStatus(Response.HTTP_OK);
|
||||
if(response.getContentType()==null) response.setContentType("text/plain;charset=utf-8");
|
||||
@@ -64,7 +74,7 @@ public class ResponseEncoder {
|
||||
writer=response.char_response;
|
||||
}else if(response.byte_response!=null){
|
||||
out=response.byte_response;
|
||||
writer=new OutputStreamWriter(out,StandardCharsets.UTF_8);
|
||||
writer=new OutputStreamWriter(out,charSet);
|
||||
}else{
|
||||
writer=new StringWriter();
|
||||
}
|
||||
@@ -132,4 +142,23 @@ public class ResponseEncoder {
|
||||
//wr.append("\n");
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+12
-4
@@ -18,16 +18,24 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
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
|
||||
ArrayList<RouteDetector> detectors=new ArrayList<>(); // route patterns ordered
|
||||
int[] indexes; // indexes for each route within regex
|
||||
Pattern regex;
|
||||
|
||||
public RoutedEndPoint() {
|
||||
public Router() {
|
||||
super("router");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void before(Request request, Response response) throws IOException {
|
||||
}
|
||||
@Override
|
||||
public void after(Request request, Response response) throws IOException {
|
||||
}
|
||||
@Override
|
||||
public void serve(Request req, Response resp) throws IOException {
|
||||
//System.out.println(req.http_request);
|
||||
@@ -135,7 +143,7 @@ public class RoutedEndPoint extends EndPoint{
|
||||
* @param target
|
||||
* @return
|
||||
*/
|
||||
public RoutedEndPoint importMethods(Object target){
|
||||
public Router importMethods(Object target){
|
||||
//RoutedEndPoint ret=new RoutedEndPoint();
|
||||
LinkedList<Method> routes=new LinkedList<>();
|
||||
Class<?> type=target.getClass();
|
||||
@@ -37,6 +37,10 @@ public class Menu extends MenuItem{
|
||||
public int getSize(){
|
||||
return items.size();
|
||||
}
|
||||
public Menu clear(){
|
||||
items.clear();
|
||||
return this;
|
||||
}
|
||||
public Menu add(MenuItem itm){
|
||||
items.add(itm);
|
||||
return this;
|
||||
|
||||
@@ -8,11 +8,11 @@ You may not use this file except in compliance with the License.
|
||||
package com.reliancy.jabba.ui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.github.jknack.handlebars.Context;
|
||||
import com.reliancy.jabba.Response;
|
||||
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("toolbar",Menu.request(Menu.LEFT));
|
||||
ret.with("feedback",Feedback.get());
|
||||
ret.with("layout","land-app");
|
||||
return ret;
|
||||
}
|
||||
public static Rendering begin(String path,Object ...sp){
|
||||
@@ -48,9 +49,9 @@ public class Rendering extends HashMap<String,Object>{
|
||||
ctx.destroy();
|
||||
}
|
||||
}
|
||||
public void end(Writer _out) throws IOException{
|
||||
public void end(Response resp) throws IOException{
|
||||
try{
|
||||
template.render(ctx,_out);
|
||||
template.render(ctx,resp.getEncoder().getWriter());
|
||||
}finally{
|
||||
ctx.destroy();
|
||||
}
|
||||
|
||||
@@ -59,16 +59,29 @@ public class Template {
|
||||
@Override
|
||||
public TemplateSource sourceAt(String location) throws IOException {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
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 HashMap<String,String> partial_map=new HashMap<>();
|
||||
static Object[] search_path;
|
||||
static HashMap<String,Template> cache=new HashMap<>();
|
||||
static{
|
||||
handlebars= new Handlebars(new HBLoader());
|
||||
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.
|
||||
*
|
||||
* @param path
|
||||
@@ -89,7 +100,7 @@ public class Template {
|
||||
* @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){
|
||||
return null;
|
||||
}else{
|
||||
@@ -102,7 +113,7 @@ public class Template {
|
||||
public static Template find(String path,Object ... sp) {
|
||||
Template ret=cache.get(path);
|
||||
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;
|
||||
ret=new Template(loc);
|
||||
cache.put(path,ret);
|
||||
@@ -110,7 +121,17 @@ public class Template {
|
||||
}
|
||||
public static Object[] search_path(Object...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}");
|
||||
com.github.jknack.handlebars.Template recipe;
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
@@ -84,7 +85,7 @@ public final class Handy {
|
||||
*/
|
||||
public static Object normalize(Class<?> clazz, Object val ) {
|
||||
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){
|
||||
String value=(String) val;
|
||||
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( Double.class==( clazz ) || double.class==( clazz )) return Double.parseDouble( value );
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
/**
|
||||
@@ -523,4 +523,39 @@ public final class Handy {
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,6 +8,7 @@ You may not use this file except in compliance with the License.
|
||||
|
||||
package com.reliancy.util;
|
||||
import java.io.File;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
@@ -42,8 +43,15 @@ public class Path {
|
||||
String database; ///< name of database or filename
|
||||
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) {
|
||||
setConnectString(connect);
|
||||
this(connect,true);
|
||||
}
|
||||
|
||||
public Path(Path in) {
|
||||
@@ -59,7 +67,7 @@ public class Path {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getConnectString();
|
||||
return assemble();
|
||||
}
|
||||
/**
|
||||
* Converts the path to a file.
|
||||
@@ -85,7 +93,7 @@ public class Path {
|
||||
if(Handy.isBlank(getHost()) && path.contains("://") && !path.contains(":///")) path=path.replace("://",":///");
|
||||
return new URL(path);
|
||||
}
|
||||
public void clear(){
|
||||
public Path clear(){
|
||||
connectstring=null;
|
||||
protocol=null;
|
||||
userid=null;
|
||||
@@ -94,9 +102,9 @@ public class Path {
|
||||
port=null;
|
||||
database=null;
|
||||
properties=null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getConnectString() {
|
||||
public String assemble() {
|
||||
if(connectstring!=null) return connectstring;
|
||||
// assemble the connect string
|
||||
StringBuilder buf=new StringBuilder();
|
||||
@@ -131,10 +139,10 @@ public class Path {
|
||||
return connectstring;
|
||||
}
|
||||
|
||||
public void setConnectString(String connect) {
|
||||
public Path parse(String connect) {
|
||||
clear();
|
||||
if (connect == null) {
|
||||
return;
|
||||
return this;
|
||||
}
|
||||
this.connectstring=connect;
|
||||
// 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);
|
||||
database = database.substring(0, st);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -411,12 +420,22 @@ public class Path {
|
||||
return str.split("&");
|
||||
}
|
||||
public static String[] splitKeyValue(String str) {
|
||||
String[] t=str.split("=");
|
||||
String[] t=Handy.split("=",str,1);
|
||||
if(t==null || t.length==0) return null;
|
||||
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) {
|
||||
return str.split("/");
|
||||
return Handy.split("/",str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,18 +19,47 @@ import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
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 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 String rewritePath(String path,Object context);
|
||||
}
|
||||
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){
|
||||
if(remap!=null) path=remap.rewritePath(path,base);
|
||||
if(remap!=null) path=remap.rewritePath(path0,base);
|
||||
if(base instanceof Class){
|
||||
URL ret=((Class<?>)base).getResource(path);
|
||||
return ret;
|
||||
@@ -57,11 +86,20 @@ public class Resources {
|
||||
URL ret=new URL((URL)base,path);
|
||||
String proto=ret.getProtocol();
|
||||
if(proto.equals("http") || proto.equals("https")){
|
||||
HttpURLConnection huc = (HttpURLConnection) ret.openConnection();
|
||||
huc.setRequestMethod("HEAD");
|
||||
int responseCode = huc.getResponseCode();
|
||||
huc.disconnect();
|
||||
if(responseCode==HttpURLConnection.HTTP_OK) return ret;
|
||||
HttpURLConnection huc = null;
|
||||
try{
|
||||
huc=(HttpURLConnection) ret.openConnection();
|
||||
huc.setRequestMethod("HEAD");
|
||||
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")){
|
||||
File f=new File(ret.getPath());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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="card">
|
||||
<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 class="card-body">
|
||||
<!--<h5 class="card-title">Special title treatment</h5>-->
|
||||
@@ -15,4 +17,4 @@
|
||||
|
||||
</div>
|
||||
{{/partial}}
|
||||
{{> frame-1c}}
|
||||
{{> __frame__}}
|
||||
@@ -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}}
|
||||
@@ -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>
|
||||
</div>
|
||||
{{/partial}}
|
||||
{{> frame-1c}}
|
||||
{{> __frame__}}
|
||||
@@ -60,7 +60,7 @@ public class TerminalTest {
|
||||
@BeforeClass
|
||||
public static void beforeAllTestMethods() {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 );
|
||||
System.out.println("Test router init...");
|
||||
JettyApp r=new JettyApp();
|
||||
RoutedEndPoint rep=new RoutedEndPoint();
|
||||
Router rep=new Router();
|
||||
rep.importMethods(r);
|
||||
rep.compile();
|
||||
//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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user