inital commit
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" output="build/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="build/classes/java/test" path="src/test/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="gradle_scope" value="test"/>
|
||||||
|
<attribute name="gradle_used_by_scope" value="test"/>
|
||||||
|
<attribute name="test" value="true"/>
|
||||||
|
</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="build-default"/>
|
||||||
|
</classpath>
|
||||||
Vendored
+35
@@ -0,0 +1,35 @@
|
|||||||
|
.vscode/
|
||||||
|
!.vscode/launch.json
|
||||||
|
|
||||||
|
### Java ###
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# BlueJ files
|
||||||
|
*.ctxt
|
||||||
|
|
||||||
|
# Mobile Tools for Java (J2ME)
|
||||||
|
.mtj.tmp/
|
||||||
|
|
||||||
|
# Package Files #
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.ear
|
||||||
|
|
||||||
|
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||||
|
hs_err_pid*
|
||||||
|
|
||||||
|
|
||||||
|
### Gradle ###
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Ignore Gradle GUI config
|
||||||
|
gradle-app.setting
|
||||||
|
|
||||||
|
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
||||||
|
!gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Cache of project
|
||||||
|
.gradletasknamecache
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>jabba</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
|
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||||
|
</natures>
|
||||||
|
<filteredResources>
|
||||||
|
<filter>
|
||||||
|
<id>1614986289528</id>
|
||||||
|
<name></name>
|
||||||
|
<type>30</type>
|
||||||
|
<matcher>
|
||||||
|
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
||||||
|
<arguments>node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
||||||
|
</matcher>
|
||||||
|
</filter>
|
||||||
|
</filteredResources>
|
||||||
|
</projectDescription>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
arguments=
|
||||||
|
auto.sync=false
|
||||||
|
build.scans.enabled=false
|
||||||
|
connection.gradle.distribution=GRADLE_DISTRIBUTION(LOCAL_INSTALLATION(C\:\\ProgramData\\chocolatey\\lib\\gradle\\tools\\gradle-7.0))
|
||||||
|
connection.project.dir=
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
gradle.user.home=
|
||||||
|
java.home=C\:/Program Files/AdoptOpenJDK/jdk-15.0.2.7-hotspot
|
||||||
|
jvm.arguments=
|
||||||
|
offline.mode=false
|
||||||
|
override.workspace.settings=true
|
||||||
|
show.console.view=true
|
||||||
|
show.executions.view=true
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
eclipse.preferences.version=1
|
||||||
|
encoding//src/main/java=UTF-8
|
||||||
|
encoding//src/test/java=UTF-8
|
||||||
|
encoding/<project>=UTF-8
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
eclipse.preferences.version=1
|
||||||
|
org.eclipse.jdt.apt.aptEnabled=false
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
eclipse.preferences.version=1
|
||||||
|
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||||
|
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||||
|
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.processAnnotations=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.release=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.source=1.8
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
activeProfiles=
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
resolveWorkspaceProjects=true
|
||||||
|
version=1
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
# Let's try makefiles on Kotlin or JAVA
|
||||||
|
# we will try to create one sample makefile to compile and create JAR files
|
||||||
|
# both java and kotlin are jvm - running compiler on single files is bad
|
||||||
|
# both java and kotlin are diffent from c as the compilers honor package structure not folders
|
||||||
|
# so we compile in src/main/java but that gets used as base by compiler
|
||||||
|
|
||||||
|
# Project Artifacts - Starts with SRC_FILES
|
||||||
|
PNAME=jabba
|
||||||
|
ENTRYPOINT=com.reliancy.jabba.Router
|
||||||
|
DIR_COMPILE=build
|
||||||
|
DIR_PACKAGE=dist
|
||||||
|
DIR_SRC=src
|
||||||
|
DIR_LIB=target/lib
|
||||||
|
MANIFEST=
|
||||||
|
MANIFEST_AUTO=$(DIR_PACKAGE)/manifest.mf
|
||||||
|
|
||||||
|
ASSETS=%.txt %.html %.css %.js %.png %.jsp
|
||||||
|
# Params
|
||||||
|
KOTLIN_BASE=$(DIR_SRC)/main/kotlin
|
||||||
|
KOTLIN_EXT = kt
|
||||||
|
JAVA_BASE=$(DIR_SRC)/main/java
|
||||||
|
JAVA_EXT = java
|
||||||
|
OBJ_EXT = class
|
||||||
|
JFLAGS=-g
|
||||||
|
KFLAGS=
|
||||||
|
KOTLIN_TODO=kotlin_todo.txt
|
||||||
|
JAVA_TODO=java_todo.txt
|
||||||
|
|
||||||
|
# Tools
|
||||||
|
PRINT := @printf
|
||||||
|
RM := @rm -rf
|
||||||
|
COPY := @cp
|
||||||
|
MOVE := @mv
|
||||||
|
JVC := javac
|
||||||
|
KTC := java -jar $(subst \,/,$(KOTLIN_HOME))/lib/kotlin-compiler.jar
|
||||||
|
TEST_IF := @test -s
|
||||||
|
JAR := @jar -cvfm
|
||||||
|
MAKEDIR := @mkdir -p
|
||||||
|
CFIND := find
|
||||||
|
FIND := @$(CFIND)
|
||||||
|
|
||||||
|
# File Sets
|
||||||
|
ALL_SRC=$(subst \,/,$(shell $(CFIND) $(DIR_SRC) -type f))
|
||||||
|
ASSETS_SRC=$(filter $(ASSETS),$(ALL_SRC))
|
||||||
|
ASSETS_JAVA:=$(subst $(JAVA_BASE),$(DIR_COMPILE),$(filter $(JAVA_BASE)/%,$(ASSETS_SRC)))
|
||||||
|
ASSETS_KOTLIN:=$(subst $(KOTLIN_BASE),$(DIR_COMPILE),$(filter $(KOTLIN_BASE)/%,$(ASSETS_SRC)))
|
||||||
|
SRC_DIRS ?=$(subst \,/,$(shell $(CFIND) $(DIR_SRC) -type d -print))
|
||||||
|
KOTLIN_SRC=$(foreach dir,$(SRC_DIRS),$(filter $(KOTLIN_BASE)/%,$(wildcard $(dir)/*.$(KOTLIN_EXT))))
|
||||||
|
KOTLIN_OBJ=$(KOTLIN_SRC:$(KOTLIN_BASE)/%.$(KOTLIN_EXT)=$(DIR_COMPILE)/%.$(OBJ_EXT))
|
||||||
|
JAVA_SRC=$(foreach dir,$(SRC_DIRS),$(filter $(JAVA_BASE)/%,$(wildcard $(dir)/*.$(JAVA_EXT))))
|
||||||
|
JAVA_OBJ=$(JAVA_SRC:$(JAVA_BASE)/%.$(JAVA_EXT)=$(DIR_COMPILE)/%.$(OBJ_EXT))
|
||||||
|
LIBS=$(foreach dir,$(DIR_LIB),$(wildcard $(dir)/*.jar))
|
||||||
|
SPACE=$() $()
|
||||||
|
CLASSPATH=$(subst $(SPACE),;,$(LIBS))
|
||||||
|
JFLAGS:= -cp '$(CLASSPATH)'
|
||||||
|
KFLAGS:= -cp '$(CLASSPATH)'
|
||||||
|
|
||||||
|
# Common Targets
|
||||||
|
.SUFFIXES:
|
||||||
|
|
||||||
|
# rule to build kotlin
|
||||||
|
$(DIR_COMPILE)/%.$(OBJ_EXT): $(KOTLIN_BASE)/%.$(KOTLIN_EXT)
|
||||||
|
$(PRINT) '\tCollecting source: %s\n' $<
|
||||||
|
$(PRINT) "$<\n" >> $(DIR_COMPILE)/$(KOTLIN_TODO)
|
||||||
|
# rule to build java
|
||||||
|
$(DIR_COMPILE)/%.$(OBJ_EXT): $(JAVA_BASE)/%.$(JAVA_EXT)
|
||||||
|
$(PRINT) '\tCollecting source: %s\n' $<
|
||||||
|
$(PRINT) "$<\n" >> $(DIR_COMPILE)/$(JAVA_TODO)
|
||||||
|
# rule to move assets - observe static pattern rules
|
||||||
|
$(ASSETS_KOTLIN): $(DIR_COMPILE)/% : $(KOTLIN_BASE)/%
|
||||||
|
$(MAKEDIR) $(dir $@)
|
||||||
|
$(COPY) --update $< $@ && printf "\tCopy asset:$< -> $@\n"
|
||||||
|
$(ASSETS_JAVA): $(DIR_COMPILE)/% : $(JAVA_BASE)/%
|
||||||
|
$(MAKEDIR) $(dir $@)
|
||||||
|
$(COPY) --update $< $@ && printf "\tCopy asset:$< -> $@\n"
|
||||||
|
|
||||||
|
#default: dist
|
||||||
|
package: compile | $(DIR_PACKAGE)
|
||||||
|
$(PRINT) "Packaging\n"
|
||||||
|
$(RM) $(DIR_COMPILE)/$(KOTLIN_TODO)
|
||||||
|
$(RM) $(DIR_COMPILE)/$(JAVA_TODO)
|
||||||
|
# $(FIND) $(BUILD_DIR) -name "META-INF" -exec rm -rf {} +
|
||||||
|
ifeq ($(MANIFEST),)
|
||||||
|
$(PRINT) "Manifest-Version: 1.0\n" >> $(MANIFEST_AUTO)
|
||||||
|
$(PRINT) "Created-By: Reliancy Makefile\n" >> $(MANIFEST_AUTO)
|
||||||
|
$(PRINT) "Class-Path: $(foreach lib,$(LIBS),lib/$(notdir $(lib)))\n" >> $(MANIFEST_AUTO)
|
||||||
|
ifneq ($(ENTRYPOINT),)
|
||||||
|
$(PRINT) "Main-Class: $(ENTRYPOINT)\n" >> $(MANIFEST_AUTO)
|
||||||
|
endif
|
||||||
|
else
|
||||||
|
$(COPY) $(MANIFEST) $(MANIFEST_AUTO)
|
||||||
|
endif
|
||||||
|
$(JAR) $(DIR_PACKAGE)/$(PNAME).jar $(MANIFEST_AUTO) -C $(DIR_COMPILE) .
|
||||||
|
compile: compile_kotlin compile_java
|
||||||
|
$(PRINT) "Compiling done.\n"
|
||||||
|
|
||||||
|
compile_kotlin: $(KOTLIN_OBJ) | $(DIR_COMPILE) $(ASSETS_KOTLIN)
|
||||||
|
ifneq ($(KOTLIN_OBJ),)
|
||||||
|
# $(PRINT) "KtSrc:$(KOTLIN_SRC)\n"
|
||||||
|
# $(PRINT) "KtObj:$(KOTLIN_OBJ)\n"
|
||||||
|
$(PRINT) 'Building Kotlin\n'
|
||||||
|
$(KTC) $(KFLAGS) -d $(DIR_COMPILE) @$(DIR_COMPILE)/$(KOTLIN_TODO)
|
||||||
|
endif
|
||||||
|
|
||||||
|
compile_java: $(JAVA_OBJ) | $(DIR_COMPILE) $(ASSETS_JAVA)
|
||||||
|
ifneq ($(JAVA_OBJ),)
|
||||||
|
$(PRINT) "JavaSrc:$(JAVA_SRC)\n"
|
||||||
|
$(PRINT) "JavaObj:$(JAVA_OBJ)\n"
|
||||||
|
$(PRINT) "Building Java\n"
|
||||||
|
@$(JVC) $(JFLAGS) -d $(DIR_COMPILE) @$(DIR_COMPILE)/$(JAVA_TODO)
|
||||||
|
endif
|
||||||
|
|
||||||
|
$(DIR_COMPILE):
|
||||||
|
$(PRINT) "prepare dir: $(DIR_COMPILE).\n"
|
||||||
|
$(MAKEDIR) $(DIR_COMPILE)
|
||||||
|
$(RM) $(DIR_COMPILE)/$(KOTLIN_TODO)
|
||||||
|
$(RM) $(DIR_COMPILE)/$(JAVA_TODO)
|
||||||
|
|
||||||
|
$(DIR_PACKAGE):
|
||||||
|
$(PRINT) "prepare dir: $(DIR_PACKAGE).\n"
|
||||||
|
$(MAKEDIR) $(DIR_PACKAGE)
|
||||||
|
$(MAKEDIR) $(DIR_PACKAGE)/lib
|
||||||
|
$(COPY) $(LIBS) $(DIR_PACKAGE)/lib/
|
||||||
|
|
||||||
|
run:
|
||||||
|
$(PRINT) "Running\n"
|
||||||
|
test:
|
||||||
|
$(PRINT) "Testing\n"
|
||||||
|
clean:
|
||||||
|
$(RM) $(DIR_COMPILE)
|
||||||
|
$(RM) $(DIR_PACKAGE)
|
||||||
|
.PHONY: package compile dirs run test clean
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Jabba the easy going java web app plumber
|
||||||
|
Jabba is a java library that gets its inspiration from Pythong flask. It will expose all the elementary features needed for deveopment of web apps and microservices.
|
||||||
|
|
||||||
|
# How to Run Things
|
||||||
|
* running a build via: gradle jar
|
||||||
|
* running a test via: gradle test
|
||||||
|
* running a continouse server via: gradle runServer, then work on code
|
||||||
|
|
||||||
|
## Things Left to Do
|
||||||
|
* ~~Complete support for demarshalling and marshalling of objects to java methods~~ on 10/4/2021
|
||||||
|
* ~~Session middleware~~
|
||||||
|
* Auth middleware supporting basic and digest, an security entities
|
||||||
|
* ~~Static file serving~~
|
||||||
|
* ~~Templating like jinja~~
|
||||||
|
* Database layer or serial/deserial system like SQL Alchemy
|
||||||
+181
@@ -0,0 +1,181 @@
|
|||||||
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'eclipse'
|
||||||
|
apply plugin: 'application'
|
||||||
|
mainClassName = 'com.reliancy.jabba.Router'
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
sourceCompatibility = 1.8
|
||||||
|
targetCompatibility = 1.8
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.eclipse.jetty:jetty-server:11.0.1"
|
||||||
|
implementation "org.slf4j:slf4j-simple:2.0.0-alpha0"
|
||||||
|
implementation 'com.hubspot.jinjava:jinjava:2.5.10'
|
||||||
|
implementation 'com.hubspot.jinjava:jinjava:2.5.10'
|
||||||
|
implementation 'com.h2database:h2:1.4.200'
|
||||||
|
// https://mvnrepository.com/artifact/org.postgresql/postgresql
|
||||||
|
implementation 'org.postgresql:postgresql:42.3.1'
|
||||||
|
// https://mvnrepository.com/artifact/com.zaxxer/HikariCP
|
||||||
|
implementation 'com.zaxxer:HikariCP:5.0.0'
|
||||||
|
|
||||||
|
testImplementation "junit:junit:4.12"
|
||||||
|
}
|
||||||
|
test {
|
||||||
|
testLogging {
|
||||||
|
// Make sure output from
|
||||||
|
// standard out or error is shown
|
||||||
|
// in Gradle output.
|
||||||
|
outputs.upToDateWhen {false}
|
||||||
|
showStandardStreams = true
|
||||||
|
exceptionFormat = 'full'
|
||||||
|
// Or we use events method:
|
||||||
|
// events 'standard_out', 'standard_error'
|
||||||
|
|
||||||
|
// Or set property events:
|
||||||
|
// events = ['standard_out', 'standard_error']
|
||||||
|
|
||||||
|
// Instead of string values we can
|
||||||
|
// use enum values:
|
||||||
|
// events org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_OUT,
|
||||||
|
// org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jar {
|
||||||
|
archiveBaseName = 'jabba'
|
||||||
|
archiveVersion = '0.1'
|
||||||
|
manifest {
|
||||||
|
attributes "Main-Class": mainClassName
|
||||||
|
attributes "Class-Path": configurations.runtimeClasspath.collect { it.getName() }.join(' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task copyToLib(type: Copy) {
|
||||||
|
into "${buildDir}/libs" from configurations.runtimeClasspath
|
||||||
|
}
|
||||||
|
build.dependsOn(copyToLib)
|
||||||
|
task fat_jar(type: Jar) {
|
||||||
|
archiveBaseName = 'fat-jabba'
|
||||||
|
archiveVersion = '0.1'
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
/*
|
||||||
|
manifest {
|
||||||
|
attributes "Main-Class": mainClassName
|
||||||
|
attributes "Class-Path": configurations.runtimeClasspath.collect { it.getName() }.join(' ')
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
from {
|
||||||
|
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||||
|
}{
|
||||||
|
exclude "META-INF/NOTICE.txt"
|
||||||
|
exclude "META-INF/LICENSE"
|
||||||
|
}
|
||||||
|
with jar
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
We define a singleton pattern using background thread to launch a blocking process.
|
||||||
|
Later we make sure to terminate previous running driver threads before starting new.
|
||||||
|
Also we split start, stop because we might not run in continouse mode.
|
||||||
|
*/
|
||||||
|
class Server implements Runnable{
|
||||||
|
static Server singleton=null;
|
||||||
|
public static Server getSingleton(){
|
||||||
|
if(singleton==null){
|
||||||
|
System.out.println("creating new server");
|
||||||
|
singleton=new Server();
|
||||||
|
}
|
||||||
|
return singleton;
|
||||||
|
}
|
||||||
|
org.slf4j.Logger log=org.slf4j.LoggerFactory.getLogger("server.driver");
|
||||||
|
Thread driver=null;
|
||||||
|
Runnable task=null;
|
||||||
|
protected Server(){}
|
||||||
|
public void info(String msg){
|
||||||
|
System.out.println(msg);
|
||||||
|
}
|
||||||
|
public Server useDriver(boolean f){
|
||||||
|
info("using driver:"+f);
|
||||||
|
if(f){
|
||||||
|
driver=new Thread(this);
|
||||||
|
driver.setName("server.driver");
|
||||||
|
}else{
|
||||||
|
driver=null;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public void run(){
|
||||||
|
info("running task");
|
||||||
|
try{
|
||||||
|
task.run();
|
||||||
|
}catch(java.lang.InterruptedException ex){
|
||||||
|
info("running task:interrupted");
|
||||||
|
}catch(org.gradle.internal.UncheckedException ex2){
|
||||||
|
info("running task:interrupted2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Server start(Runnable c){
|
||||||
|
info("starting server");
|
||||||
|
this.task=c;
|
||||||
|
if(driver!=null)
|
||||||
|
driver.start();
|
||||||
|
else
|
||||||
|
this.run();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Server stop(){
|
||||||
|
info("stopping server");
|
||||||
|
if(driver!=null){
|
||||||
|
driver.interrupt();
|
||||||
|
driver.join();
|
||||||
|
}
|
||||||
|
for(Thread th:Thread.getAllStackTraces().keySet()){
|
||||||
|
if(th.getName().equalsIgnoreCase("executor")){
|
||||||
|
info("cleaning up stale driver:"+th.toString())
|
||||||
|
th.stop();
|
||||||
|
}
|
||||||
|
if(th.getName().equalsIgnoreCase("server.driver")){
|
||||||
|
info("cleaning up stale driver:"+th.toString())
|
||||||
|
th.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task runServer{
|
||||||
|
inputs.files 'src'
|
||||||
|
doFirst {
|
||||||
|
//println 'This is executed first during the execution phase.'
|
||||||
|
Server.getSingleton().stop();
|
||||||
|
Server.getSingleton().useDriver(project.gradle.startParameter.continuous);
|
||||||
|
}
|
||||||
|
doLast {
|
||||||
|
//println 'This is executed last during the execution phase.'
|
||||||
|
Server.getSingleton().start({
|
||||||
|
project.javaexec {
|
||||||
|
classpath = project.sourceSets.main.runtimeClasspath
|
||||||
|
main = mainClassName
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
group = 'Run' // <-- change the name as per your need
|
||||||
|
description = 'execute run but continously'
|
||||||
|
classpath sourceSets.main.runtimeClasspath // <-- Don't change this
|
||||||
|
main = mainClassName
|
||||||
|
//args "arg1", "arg2"
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
// build.gradle
|
||||||
|
eclipse.classpath {
|
||||||
|
defaultOutputDir = file("build") ///default
|
||||||
|
file.whenMerged { cp ->
|
||||||
|
cp.entries.forEach { cpe ->
|
||||||
|
if (cpe instanceof org.gradle.plugins.ide.eclipse.model.SourceFolder) {
|
||||||
|
cpe.output = cpe.output.replace "bin/", "build/classes/java/"
|
||||||
|
}
|
||||||
|
if (cpe instanceof org.gradle.plugins.ide.eclipse.model.Output) {
|
||||||
|
cpe.path = cpe.path.replace "bin/", "build/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.reliancy.jabba</groupId>
|
||||||
|
<artifactId>jabba</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>jabba</name>
|
||||||
|
<url>http://www.reliancy.com</url>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.11</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-server</artifactId>
|
||||||
|
<version>11.0.7</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-servlet</artifactId>
|
||||||
|
<version>11.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hubspot.jinjava</groupId>
|
||||||
|
<artifactId>jinjava</artifactId>
|
||||||
|
<version>2.5.10</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<version>3.1.2</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy-dependencies</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>${project.build.directory}/lib</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<!-- Maven Assembly Plugin -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<version>2.4.1</version>
|
||||||
|
<configuration>
|
||||||
|
<!-- get all project dependencies -->
|
||||||
|
<descriptorRefs>
|
||||||
|
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||||
|
</descriptorRefs>
|
||||||
|
<!-- MainClass in mainfest make a executable jar -->
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>com.reliancy.jabba.Router</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>make-assembly</id>
|
||||||
|
<!-- bind to the packaging phase -->
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
|
<version>3.0.0</version>
|
||||||
|
<configuration>
|
||||||
|
<mainClass>com.reliancy.jabba.Router</mainClass>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-clean-plugin</artifactId>
|
||||||
|
<version>3.1.0</version>
|
||||||
|
</plugin>
|
||||||
|
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
|
<version>3.0.2</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.0</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>2.22.1</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>3.0.2</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-install-plugin</artifactId>
|
||||||
|
<version>2.5.2</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-deploy-plugin</artifactId>
|
||||||
|
<version>2.8.2</version>
|
||||||
|
</plugin>
|
||||||
|
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-site-plugin</artifactId>
|
||||||
|
<version>3.7.1</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-project-info-reports-plugin</artifactId>
|
||||||
|
<version>3.0.0</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</pluginManagement>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
package com.reliancy.dbo;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import com.reliancy.util.CloseableIterator;
|
||||||
|
|
||||||
|
/** Description of a terminal operation with a slice of dbo objects as input or output.
|
||||||
|
* This object is not just for reading but also bulk updating.
|
||||||
|
* It will be used to describe a multi DBO read or write and to then also track results.
|
||||||
|
*/
|
||||||
|
public class Action implements Iterable<DBO>,CloseableIterator<DBO>{
|
||||||
|
public static enum Type{
|
||||||
|
NONE,LOAD,SAVE,DELETE
|
||||||
|
}
|
||||||
|
Terminal terminal;
|
||||||
|
Type type;
|
||||||
|
Entity entity;
|
||||||
|
Object[] params;
|
||||||
|
CloseableIterator<DBO> items;
|
||||||
|
int limit,offset;
|
||||||
|
Condition filter;
|
||||||
|
|
||||||
|
public Action(){
|
||||||
|
type=Type.NONE;
|
||||||
|
}
|
||||||
|
public Action(Type t){
|
||||||
|
type=t;
|
||||||
|
}
|
||||||
|
public Action(Terminal t){
|
||||||
|
terminal=t;
|
||||||
|
type=Type.NONE;
|
||||||
|
}
|
||||||
|
public Action execute() throws IOException{
|
||||||
|
return terminal.execute(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Terminal getTerminal() {
|
||||||
|
return terminal;
|
||||||
|
}
|
||||||
|
public Action setTerminal(Terminal terminal) {
|
||||||
|
this.terminal = terminal;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Type getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
public Action setType(Type type) {
|
||||||
|
this.type = type;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Entity getEntity() {
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
public Action setEntity(Entity entity) {
|
||||||
|
this.entity = entity;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public void clear(){
|
||||||
|
terminal=null;
|
||||||
|
type=Type.NONE;
|
||||||
|
entity=null;
|
||||||
|
setItems((DBO)null);
|
||||||
|
}
|
||||||
|
public Action load(Entity ent){
|
||||||
|
type=Type.LOAD;
|
||||||
|
entity=ent;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Action save(Entity ent){
|
||||||
|
type=Type.SAVE;
|
||||||
|
entity=ent;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Action delete(Entity ent){
|
||||||
|
type=Type.DELETE;
|
||||||
|
entity=ent;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Action params(Object...p){
|
||||||
|
params=p;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Action setItems(DBO ...itms){
|
||||||
|
CloseableIterator<DBO> it=null;
|
||||||
|
if(itms!=null){
|
||||||
|
it=new CloseableIterator<DBO>() {
|
||||||
|
private int index = 0;
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return itms.length > index;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public DBO next() {
|
||||||
|
return itms[index++];
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return setItems(it);
|
||||||
|
}
|
||||||
|
public Action setItems(CloseableIterator<DBO> itms){
|
||||||
|
if(items!=null){
|
||||||
|
try {
|
||||||
|
items.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items=itms;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
protected CloseableIterator<DBO> getItems(){
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Iterator<DBO> iterator() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return items!=null?items.hasNext():false;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public DBO next() {
|
||||||
|
return items.next();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
if(items!=null){
|
||||||
|
items.close();
|
||||||
|
if(terminal!=null) terminal.end(this);
|
||||||
|
}
|
||||||
|
items=null;
|
||||||
|
}
|
||||||
|
public Action limit(int max) {
|
||||||
|
limit=max;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Action if_filter(Condition... c){
|
||||||
|
if(c!=null){
|
||||||
|
if(c.length>1) filter=Condition.and(c);
|
||||||
|
else filter=c[0];
|
||||||
|
}else{
|
||||||
|
filter=null;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Action if_pk(Object[] id) {
|
||||||
|
Field pk=entity.getPk();
|
||||||
|
return if_filter(Condition.eq(pk,id));
|
||||||
|
}
|
||||||
|
public DBO first() {
|
||||||
|
try{
|
||||||
|
return items!=null?items.next():null;
|
||||||
|
}finally{
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public boolean isDone(){
|
||||||
|
return items==null || items.hasNext()==false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.reliancy.dbo;
|
||||||
|
|
||||||
|
/** constraint on a field.
|
||||||
|
* conditions can be leafs or groups such as and,or,not
|
||||||
|
*/
|
||||||
|
public class Condition {
|
||||||
|
public static abstract class Op{
|
||||||
|
public abstract boolean met(Condition c);
|
||||||
|
}
|
||||||
|
public static Op AND=new Op(){
|
||||||
|
public boolean met(Condition c){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static Op OR=new Op(){
|
||||||
|
public boolean met(Condition c){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static Op NOT=new Op(){
|
||||||
|
public boolean met(Condition c){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static Op EQ=new Op(){
|
||||||
|
public boolean met(Condition c){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static Op NEQ=new Op(){
|
||||||
|
public boolean met(Condition c){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static Op GT=new Op(){
|
||||||
|
public boolean met(Condition c){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static Op GTE=new Op(){
|
||||||
|
public boolean met(Condition c){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static Op LT=new Op(){
|
||||||
|
public boolean met(Condition c){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static Op LTE=new Op(){
|
||||||
|
public boolean met(Condition c){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static Op LIKE=new Op(){
|
||||||
|
public boolean met(Condition c){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static Op IN=new Op(){
|
||||||
|
public boolean met(Condition c){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static Condition and(Condition... c) {
|
||||||
|
return new Condition(AND,c);
|
||||||
|
}
|
||||||
|
public static Condition eq(Field pk, Object... id) {
|
||||||
|
return new Condition(EQ,pk,id);
|
||||||
|
}
|
||||||
|
Op code;
|
||||||
|
Object[] args;
|
||||||
|
public Condition(Op code,Field f,Object val){
|
||||||
|
this.code=code;
|
||||||
|
args=new Object[]{f,val};
|
||||||
|
}
|
||||||
|
public Condition(Op code,Condition ... sub){
|
||||||
|
this.code=code;
|
||||||
|
args=sub;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.reliancy.dbo;
|
||||||
|
|
||||||
|
|
||||||
|
/** Instance of an entity, usually a row in a table.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class DBO{
|
||||||
|
public static enum Status{
|
||||||
|
NEW,USED,DELETED,COMPUTED
|
||||||
|
}
|
||||||
|
Terminal terminal;
|
||||||
|
Entity type;
|
||||||
|
Status status;
|
||||||
|
public DBO() {
|
||||||
|
}
|
||||||
|
public Terminal getTerminal() {
|
||||||
|
return terminal;
|
||||||
|
}
|
||||||
|
public void setTerminal(Terminal terminal) {
|
||||||
|
this.terminal = terminal;
|
||||||
|
}
|
||||||
|
public Entity getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
public void setType(Entity type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
public Status getStatus(){
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
public void setStatus(Status s) {
|
||||||
|
this.status = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.reliancy.dbo;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import com.reliancy.rec.Hdr;
|
||||||
|
|
||||||
|
/** Describes an object structure, usually a table.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class Entity extends Hdr{
|
||||||
|
static final HashMap<String,Entity> registry=new HashMap<>();
|
||||||
|
public static final void publish(Entity ent){
|
||||||
|
registry.put(ent.getName(),ent);
|
||||||
|
}
|
||||||
|
public static final void retract(Entity ent){
|
||||||
|
registry.values().remove(ent);
|
||||||
|
}
|
||||||
|
public static final Entity recall(String name){
|
||||||
|
return registry.get(name);
|
||||||
|
}
|
||||||
|
public static final Entity recall(Class<?> cls){
|
||||||
|
return recall(cls.getSimpleName());
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* this method will analyze a DBO class and forumate an Entity object out of it.
|
||||||
|
* @param cls
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static final Entity publish(Class<? extends DBO> cls){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Entity base;
|
||||||
|
String dbName;
|
||||||
|
Field pk;
|
||||||
|
public Entity(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
public Field getPk(){
|
||||||
|
return pk;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.reliancy.dbo;
|
||||||
|
|
||||||
|
import com.reliancy.rec.Slot;
|
||||||
|
/**
|
||||||
|
* Description of a column or property.
|
||||||
|
*/
|
||||||
|
public class Field extends Slot {
|
||||||
|
|
||||||
|
public Field(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
public Field(String name,Class<?> typ) {
|
||||||
|
super(name,typ);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.reliancy.dbo;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import com.reliancy.util.Path;
|
||||||
|
import com.zaxxer.hikari.HikariConfig;
|
||||||
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
|
|
||||||
|
public class SQLTerminal implements Terminal{
|
||||||
|
HikariConfig config = new HikariConfig();
|
||||||
|
HikariDataSource ds;
|
||||||
|
Path url;
|
||||||
|
public SQLTerminal(String url){
|
||||||
|
this.url=new Path(url);
|
||||||
|
String proto=this.url.getProtocol();
|
||||||
|
if(!proto.startsWith("jdbc:")) proto="jdbc:"+proto;
|
||||||
|
String u=proto+"://"+this.url.getHost()+":"+this.url.getPort()+"/"+this.url.getDatabase();
|
||||||
|
config.setJdbcUrl(u);
|
||||||
|
config.setUsername(this.url.getUserid());
|
||||||
|
config.setPassword(this.url.getPassword());
|
||||||
|
config.addDataSourceProperty( "cachePrepStmts" , "true" );
|
||||||
|
config.addDataSourceProperty( "prepStmtCacheSize" , "250" );
|
||||||
|
//config.addDataSourceProperty( "prepStmtCacheSqlLimit" , "2048" );
|
||||||
|
ds = new HikariDataSource( config );
|
||||||
|
}
|
||||||
|
public Connection getConnection() throws SQLException{
|
||||||
|
return ds.getConnection();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Action execute(Action q) throws IOException {
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.reliancy.dbo;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/** Endpoint for dbo objects.
|
||||||
|
* this interface will implemet CRUD plus control over a databse or folder.
|
||||||
|
* 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 action will be a read or write query with session management.
|
||||||
|
*/
|
||||||
|
public interface Terminal {
|
||||||
|
public Action execute(Action q) throws IOException;
|
||||||
|
|
||||||
|
public default Action begin(){
|
||||||
|
return begin(null);
|
||||||
|
}
|
||||||
|
public default Action begin(String sig){
|
||||||
|
return new Action(this);
|
||||||
|
}
|
||||||
|
public default void end(Action act){
|
||||||
|
act.clear();
|
||||||
|
}
|
||||||
|
public default Terminal meta(Entity ent){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public default DBO load(Entity ent,Object...id) throws IOException {
|
||||||
|
String sig="/"+ent.getName()+"/load";
|
||||||
|
try(Action act=begin(sig).load(ent).limit(1).if_pk(id).execute()){
|
||||||
|
return act.first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public default boolean save(DBO rec) throws IOException{
|
||||||
|
Entity ent=rec.getType();
|
||||||
|
String sig="/"+ent.getName()+"/save";
|
||||||
|
try(Action act=begin(sig).save(ent).setItems(rec).execute()){
|
||||||
|
return act.isDone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public default boolean delete(DBO rec) throws IOException {
|
||||||
|
Entity ent=rec.getType();
|
||||||
|
String sig="/"+ent.getName()+"/delete";
|
||||||
|
try(Action act=begin(sig).delete(ent).setItems(rec).execute()){
|
||||||
|
return act.isDone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.reliancy.jabba;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import com.reliancy.jabbasec.SecurityActor;
|
||||||
|
|
||||||
|
public class AppSession implements Session{
|
||||||
|
final String id;
|
||||||
|
final HashMap<String,Object> values;
|
||||||
|
long timeCreated;
|
||||||
|
long lastActive;
|
||||||
|
long maxAge;
|
||||||
|
SecurityActor user;
|
||||||
|
|
||||||
|
public AppSession(String id){
|
||||||
|
this.id=id;
|
||||||
|
values=new HashMap<>();
|
||||||
|
lastActive=timeCreated=System.currentTimeMillis();
|
||||||
|
maxAge=1000*60*15;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void setValue(String key, Object val) {
|
||||||
|
if(val!=null) values.put(key,val);
|
||||||
|
else values.remove(key);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Object getValue(String key) {
|
||||||
|
return values.get(key);
|
||||||
|
}
|
||||||
|
public long getTimeInactive(){
|
||||||
|
return System.currentTimeMillis()-lastActive;
|
||||||
|
}
|
||||||
|
public void setLastActive(){
|
||||||
|
lastActive=System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
public boolean isExpired(){
|
||||||
|
return getTimeInactive()>maxAge;
|
||||||
|
}
|
||||||
|
/** allows specialized appsessions to register with more ids. */
|
||||||
|
protected void onPublish(HashMap<String,AppSession> directory){
|
||||||
|
}
|
||||||
|
/** allows specialized appsessions to deregister with more ids. */
|
||||||
|
protected void onRetract(HashMap<String,AppSession> directory){
|
||||||
|
}
|
||||||
|
static HashMap<String,AppSession> instances=new HashMap<>();
|
||||||
|
public static AppSession getInstance(String id){
|
||||||
|
return instances.get(id);
|
||||||
|
}
|
||||||
|
public static void setInstance(String id,AppSession ss){
|
||||||
|
AppSession old=getInstance(id);
|
||||||
|
if(ss!=null){
|
||||||
|
if(ss==old) return; // already published
|
||||||
|
instances.put(id,ss);
|
||||||
|
ss.onPublish(instances);
|
||||||
|
}else{
|
||||||
|
if(ss==old) return; // already retracted
|
||||||
|
old.onRetract(instances);
|
||||||
|
instances.remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public SecurityActor getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
public void setUser(SecurityActor user){
|
||||||
|
this.user=user;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.reliancy.jabba;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/** AppSession middleware will inject an appsession object into callsession.
|
||||||
|
* During each request,response we will if not alrady present extract a cookie or param
|
||||||
|
* and based on it install an app wide sesson dictionary.
|
||||||
|
*/
|
||||||
|
public class AppSessionFilter extends Processor{
|
||||||
|
public static final String KEY_NAME="jbssid";
|
||||||
|
public AppSessionFilter() {
|
||||||
|
super(AppSessionFilter.class.getSimpleName().toLowerCase());
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void before(Request request, Response response) throws IOException {
|
||||||
|
String ssid=(String)request.getParam(KEY_NAME,null);
|
||||||
|
if(ssid==null){
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
ssid=uuid.toString();
|
||||||
|
}
|
||||||
|
AppSession ss=AppSession.getInstance(ssid);
|
||||||
|
if(ss!=null){
|
||||||
|
if(ss.isExpired()){
|
||||||
|
// this app sessin expired - create a new one
|
||||||
|
ss=new AppSession(ssid);
|
||||||
|
AppSession.setInstance(ssid, ss);
|
||||||
|
}else{
|
||||||
|
// this session is good
|
||||||
|
ss.setLastActive();
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
// no session available
|
||||||
|
ss=new AppSession(ssid);
|
||||||
|
AppSession.setInstance(ssid, ss);
|
||||||
|
}
|
||||||
|
CallSession css=CallSession.getInstance();
|
||||||
|
css.setAppSession(ss);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void after(Request request, Response response) throws IOException {
|
||||||
|
CallSession css=CallSession.getInstance();
|
||||||
|
AppSession ss=(AppSession) css.getAppSession();
|
||||||
|
response.setCookie(KEY_NAME,ss.id,15*60,false);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void serve(Request request, Response response) throws IOException{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package com.reliancy.jabba;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread local object that lets us access some variables in specialized handler methods.
|
||||||
|
* For example request and response objects are accessible.
|
||||||
|
* The session is updated at process phase of each processor.
|
||||||
|
*/
|
||||||
|
public class CallSession implements Session{
|
||||||
|
ArrayList<Processor> callers=new ArrayList<>();
|
||||||
|
Session appSession;
|
||||||
|
Request request;
|
||||||
|
Response response;
|
||||||
|
|
||||||
|
public CallSession(){
|
||||||
|
}
|
||||||
|
protected void end(){
|
||||||
|
appSession=null;
|
||||||
|
request=null;
|
||||||
|
response=null;
|
||||||
|
callers.clear();
|
||||||
|
}
|
||||||
|
protected void begin(Session ss,Request req,Response resp){
|
||||||
|
appSession=ss;
|
||||||
|
request=req;
|
||||||
|
response=resp;
|
||||||
|
callers.clear();
|
||||||
|
}
|
||||||
|
protected void enter(Processor c){callers.add(c);}
|
||||||
|
protected void leave(Processor c){
|
||||||
|
int len=callers.size();
|
||||||
|
int at=len-1;
|
||||||
|
Processor last=len>0?callers.get(at):null;
|
||||||
|
if(c!=null && c==last){
|
||||||
|
callers.remove(len-1);
|
||||||
|
}else if(len>0 && (at=callers.indexOf(c))!=-1){
|
||||||
|
// bad last is not same c, some processors have not left properly
|
||||||
|
do{
|
||||||
|
last=callers.remove(callers.size()-1);
|
||||||
|
}while(last!=c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void setValue(String key, Object val) {
|
||||||
|
if(appSession!=null) appSession.setValue(key, val);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Object getValue(String key) {
|
||||||
|
return appSession!=null?appSession.getValue(key):null;
|
||||||
|
}
|
||||||
|
public void setAppSession(Session ss) {
|
||||||
|
appSession=ss;
|
||||||
|
}
|
||||||
|
public Session getAppSession() {
|
||||||
|
return appSession;
|
||||||
|
}
|
||||||
|
public Request getRequest() {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
public void setRequest(Request request) {
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
public Response getResponse() {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
public void setResponse(Response response) {
|
||||||
|
this.response = response;
|
||||||
|
}
|
||||||
|
public Processor getCaller() {
|
||||||
|
int len=callers.size();
|
||||||
|
return len>0?callers.get(len-1):null;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Will return current session given the call stack.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static ThreadLocal<CallSession> instance=new ThreadLocal<>();
|
||||||
|
public static CallSession getInstance(){
|
||||||
|
CallSession ret=instance.get();
|
||||||
|
if(ret==null) instance.set(ret=new CallSession());
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.reliancy.jabba;
|
||||||
|
|
||||||
|
public interface Config {
|
||||||
|
public void load();
|
||||||
|
public void save();
|
||||||
|
public String getId();
|
||||||
|
public Object getProperty(String key,Object def);
|
||||||
|
public Config setProperty(String key,Object val);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.reliancy.jabba;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public abstract class EndPoint extends Processor{
|
||||||
|
|
||||||
|
public EndPoint(String id) {
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void before(Request request, Response response) throws IOException {
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void after(Request request, Response response) throws IOException {
|
||||||
|
}
|
||||||
|
public abstract void serve(Request request, Response response) throws IOException;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package com.reliancy.jabba;
|
||||||
|
import com.reliancy.util.Resources;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class FileServer extends EndPoint implements Resources.PathRewrite{
|
||||||
|
public static interface Filter{
|
||||||
|
boolean isAcceptable(String path);
|
||||||
|
}
|
||||||
|
public static class ExtFilter implements Filter{
|
||||||
|
final String[] allowed;
|
||||||
|
public ExtFilter(String ...ext){allowed=ext;}
|
||||||
|
@Override
|
||||||
|
public boolean isAcceptable(String path) {
|
||||||
|
if(allowed.length==0) return true;
|
||||||
|
for(String ext:allowed) if(path.endsWith(ext)) return true;
|
||||||
|
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().info("serving:"+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
|
||||||
|
System.out.println("RES:"+f);
|
||||||
|
writeResource(f,response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response.setStatus(Response.HTTP_NOT_FOUND);
|
||||||
|
response.getEncoder().writeln("missing file:{0}",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;
|
||||||
|
}
|
||||||
|
public FileServer setDiskPrefix(String prefix){
|
||||||
|
diskPrefix=prefix;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public FileServer setClassPrefix(String prefix){
|
||||||
|
classPrefix=prefix;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Will render a file to response.
|
||||||
|
* @param f
|
||||||
|
* @param response
|
||||||
|
*/
|
||||||
|
protected void writeResource(URL f, Response response) throws IOException{
|
||||||
|
//log().info("writing:"+f);
|
||||||
|
ResponseEncoder enc=response.getEncoder();
|
||||||
|
try(InputStream is=f.openStream()){
|
||||||
|
String ctype=HTTP.guess_mime(f);
|
||||||
|
response.setStatus(Response.HTTP_OK);
|
||||||
|
response.setContentType(ctype);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Object[] getSearchPath(String url_path){
|
||||||
|
return map.get(url_path);
|
||||||
|
}
|
||||||
|
public Filter getFilter(String url_path){
|
||||||
|
return filt.get(url_path);
|
||||||
|
}
|
||||||
|
public Stream<String> streamRoutes() {
|
||||||
|
return map.keySet().stream();
|
||||||
|
}
|
||||||
|
public Iterator<String> enumRoutes(){
|
||||||
|
return map.keySet().iterator();
|
||||||
|
}
|
||||||
|
public void exportRoutes(RouterEndPoint rep) {
|
||||||
|
streamRoutes().forEach(up->rep.addRoute("GET",up+".*",this));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.reliancy.jabba;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/** HTTP related methods and classes. */
|
||||||
|
public final class HTTP {
|
||||||
|
public static HashMap<String,String> MIME_MAP=new HashMap<>();
|
||||||
|
public static class Header{
|
||||||
|
public String key;
|
||||||
|
public String value;
|
||||||
|
public Header(String k, String v){
|
||||||
|
key=k;value=v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static class Cookie{
|
||||||
|
public String key;
|
||||||
|
public String value;
|
||||||
|
public int maxAge;
|
||||||
|
public boolean secure;
|
||||||
|
public Cookie(String k,String v, int maxAge, boolean sec){
|
||||||
|
key=k;value=v;this.maxAge=maxAge;secure=sec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static String ext2mime(String ext){
|
||||||
|
if(MIME_MAP.isEmpty()){
|
||||||
|
MIME_MAP.put("ico","image/x-icon");
|
||||||
|
MIME_MAP.put("js","application/javascript");
|
||||||
|
MIME_MAP.put("doc","application/msword");
|
||||||
|
MIME_MAP.put("pdf","application/pdf");
|
||||||
|
MIME_MAP.put("zip","application/zip");
|
||||||
|
MIME_MAP.put("gif","image/gif");
|
||||||
|
MIME_MAP.put("jpg","image/jpeg");
|
||||||
|
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("css","text/css");
|
||||||
|
MIME_MAP.put("csv","text/csv");
|
||||||
|
MIME_MAP.put("html","text/html");
|
||||||
|
MIME_MAP.put("htm","text/html");
|
||||||
|
MIME_MAP.put("xml","text/xml");
|
||||||
|
}
|
||||||
|
return MIME_MAP.get(ext);
|
||||||
|
}
|
||||||
|
public static String guess_mime(Object ret) {
|
||||||
|
if(ret instanceof CharSequence){
|
||||||
|
CharSequence retstr=(CharSequence)ret;
|
||||||
|
if(retstr.length()>0 && retstr.charAt(0)=='<') return "text/html";
|
||||||
|
if(retstr.length()>0 && "{[".indexOf(retstr.charAt(0))!=-1) return "application/json";
|
||||||
|
return "text/plain";
|
||||||
|
}
|
||||||
|
if(ret instanceof byte[]){
|
||||||
|
return "application/octet-stream";
|
||||||
|
}
|
||||||
|
if(ret instanceof File || ret instanceof URL){
|
||||||
|
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 null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package com.reliancy.jabba;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Parameter;
|
||||||
|
|
||||||
|
import com.reliancy.util.Handy;
|
||||||
|
|
||||||
|
public class MethodEndPoint extends EndPoint{
|
||||||
|
enum InvokeProfile{
|
||||||
|
PLAIN, // no return, request, response as argument
|
||||||
|
NOARG, // no arguments, possible return
|
||||||
|
FULL, // one or more arguments need to do casting
|
||||||
|
|
||||||
|
}
|
||||||
|
Route route;
|
||||||
|
Object target;
|
||||||
|
Method method;
|
||||||
|
Parameter[] params;
|
||||||
|
Class<?> retType;
|
||||||
|
InvokeProfile invokeType;
|
||||||
|
|
||||||
|
public MethodEndPoint(Object target,Method m,Route r) {
|
||||||
|
super(target.getClass().getSimpleName()+"."+m.getName());
|
||||||
|
this.route=r;
|
||||||
|
this.target=target;
|
||||||
|
this.method=m;
|
||||||
|
this.params=m.getParameters();
|
||||||
|
this.retType=m.getReturnType();
|
||||||
|
this.invokeType=InvokeProfile.FULL;
|
||||||
|
if(params.length==2 && params[0].getType()==Request.class && params[1].getType()==Response.class){
|
||||||
|
invokeType=InvokeProfile.PLAIN;
|
||||||
|
}
|
||||||
|
if(params.length==0){
|
||||||
|
invokeType=InvokeProfile.NOARG;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serve(Request request, Response response) throws IOException{
|
||||||
|
log().info("Serving method....{}",invokeType);
|
||||||
|
try{
|
||||||
|
Object ret=null;
|
||||||
|
switch(invokeType){
|
||||||
|
case PLAIN:{ // plain profile just passes req,resp
|
||||||
|
method.invoke(target,request,response);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NOARG:{ // no args will not pass any arguments, will deal with return (marshalling)
|
||||||
|
ret=method.invoke(target);
|
||||||
|
encodeResponse(ret,response);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:{
|
||||||
|
// here we do full unmarshalling, marshalling
|
||||||
|
Object[] argVals=decodeRequest(request);
|
||||||
|
ret=method.invoke(target,argVals);
|
||||||
|
encodeResponse(ret,response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(Exception ex2){
|
||||||
|
if(ex2 instanceof IOException) throw ((IOException)ex2);
|
||||||
|
else throw new IOException(ex2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public String getPath() {
|
||||||
|
String ret=route.path();
|
||||||
|
if(!ret.startsWith("/")) ret="/"+ret;
|
||||||
|
ret=ret.replace("{method}",method.getName());
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
protected Object[] decodeRequest(Request request){
|
||||||
|
Object[] argVals=new Object[params.length];
|
||||||
|
for(int i=0;i<argVals.length;i++){
|
||||||
|
Parameter p=params[i];
|
||||||
|
Class<?> cls=p.getType();
|
||||||
|
String byName=p.getName();
|
||||||
|
String byPos="_arg"+i;
|
||||||
|
Object val=request.getParam(byName,request.getParam(byPos,null)); // get by name or pos
|
||||||
|
argVals[i]=Handy.normalize(cls,val);
|
||||||
|
}
|
||||||
|
return argVals;
|
||||||
|
}
|
||||||
|
protected void encodeResponse(Object ret, Response response) throws IOException{
|
||||||
|
if(ret instanceof Response){
|
||||||
|
// we have a response return - take its status and content type
|
||||||
|
Response resp=(Response)ret;
|
||||||
|
if(resp!=response){
|
||||||
|
response.setStatus(resp.getStatus());
|
||||||
|
response.setContentType(resp.getContentType());
|
||||||
|
resp.exportContent(response.getEncoder());
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
// we do not have a response but must set status, content type
|
||||||
|
String ctype=route.return_mime();
|
||||||
|
if(Handy.isBlank(ctype)) ctype=HTTP.guess_mime(ret);
|
||||||
|
response.setContentType(ctype);
|
||||||
|
if(ret!=null){
|
||||||
|
response.getEncoder().writeObject(ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package com.reliancy.jabba;
|
||||||
|
|
||||||
|
/** uri path decoded into tokens. */
|
||||||
|
public class Path {
|
||||||
|
String url;
|
||||||
|
String schema;
|
||||||
|
String host;
|
||||||
|
String db;
|
||||||
|
String query;
|
||||||
|
String[] db_parts;
|
||||||
|
public Path(String url) {
|
||||||
|
this.url = url.trim();
|
||||||
|
db=this.url;
|
||||||
|
// extract schema
|
||||||
|
int schi=db.indexOf("://");
|
||||||
|
if(schi!=-1){
|
||||||
|
schema=db.substring(0,schi);
|
||||||
|
db=db.substring(schi+3);
|
||||||
|
}
|
||||||
|
//extract host
|
||||||
|
schi=db.indexOf("/");
|
||||||
|
if(schi>0){
|
||||||
|
host=db.substring(0,schi);
|
||||||
|
db=db.substring(schi);
|
||||||
|
}
|
||||||
|
// extract query
|
||||||
|
schi=db.indexOf("?");
|
||||||
|
if(schi>0){
|
||||||
|
query=db.substring(schi+1);
|
||||||
|
db=db.substring(0,schi);
|
||||||
|
}
|
||||||
|
db_parts=db.split("/");
|
||||||
|
}
|
||||||
|
public String toString(){
|
||||||
|
return getURL();
|
||||||
|
}
|
||||||
|
public String getURL() {
|
||||||
|
if(url==null){
|
||||||
|
StringBuilder buf=new StringBuilder();
|
||||||
|
if(schema!=null) buf.append(schema).append("://");
|
||||||
|
if(host!=null) buf.append(host);
|
||||||
|
String db=getDB();
|
||||||
|
if(db!=null) buf.append(db);
|
||||||
|
String q=getQuery();
|
||||||
|
if(q!=null) buf.append("?").append(q);
|
||||||
|
url=buf.toString();
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
public void setURL(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
public String getSchema() {
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
public void setSchema(String schema) {
|
||||||
|
this.schema = schema;
|
||||||
|
}
|
||||||
|
public String getHost() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
public void setHost(String host) {
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
public String getDB() {
|
||||||
|
if(db==null && db_parts!=null) db="/"+String.join("/",db_parts);
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
public void setDB(String db) {
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
public String[] getDBParts() {
|
||||||
|
return db_parts;
|
||||||
|
}
|
||||||
|
public void setDBParts(String...parts) {
|
||||||
|
db_parts=parts;
|
||||||
|
db=null;
|
||||||
|
}
|
||||||
|
public String getQuery() {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
public void setQuery(String query) {
|
||||||
|
this.query = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package com.reliancy.jabba;
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public abstract class Processor {
|
||||||
|
protected Processor next;
|
||||||
|
protected String id;
|
||||||
|
protected boolean active;
|
||||||
|
protected Config config;
|
||||||
|
protected Logger logger;
|
||||||
|
|
||||||
|
public Processor(String id){
|
||||||
|
next=null;
|
||||||
|
this.id=id!=null?id:this.getClass().getSimpleName().toLowerCase();
|
||||||
|
active=true;
|
||||||
|
}
|
||||||
|
public String getId(){
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Processor getNext() {
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
public void setNext(Processor next) {
|
||||||
|
this.next = next;
|
||||||
|
}
|
||||||
|
public boolean isActive() {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
public void setActive(boolean active) {
|
||||||
|
this.active = active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Config getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
public void setConfig(Config config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Main event processing chain.
|
||||||
|
* Will go down the chain until result code is set.
|
||||||
|
* @param request
|
||||||
|
* @param response
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public void process(Request request,Response response) throws IOException {
|
||||||
|
CallSession ss=CallSession.getInstance();
|
||||||
|
try{
|
||||||
|
ss.enter(this);
|
||||||
|
if(!active){
|
||||||
|
if(next!=null) next.process(request, response);
|
||||||
|
}else{
|
||||||
|
before(request, response);
|
||||||
|
if(response.getStatus()==null) serve(request, response);
|
||||||
|
if(next!=null && response.getStatus()==null) next.process(request, response);
|
||||||
|
after(request, response);
|
||||||
|
}
|
||||||
|
}finally{
|
||||||
|
ss.leave(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void begin(Config conf){
|
||||||
|
this.config=conf;
|
||||||
|
};
|
||||||
|
public void end(){
|
||||||
|
this.config=null;
|
||||||
|
};
|
||||||
|
protected Logger log(){
|
||||||
|
// prefer local over central one
|
||||||
|
Logger ret=logger!=null?logger:(config!=null?(Logger)config.getProperty("logger",null):null);
|
||||||
|
// if none provided install a fresh one locally
|
||||||
|
if(ret==null) ret=logger=LoggerFactory.getLogger(this.getId());
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
public abstract void before(Request request,Response response) throws IOException;
|
||||||
|
public abstract void after(Request request,Response response) throws IOException;
|
||||||
|
public abstract void serve(Request request,Response response) throws IOException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package com.reliancy.jabba;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.reliancy.util.Handy;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.Cookie;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
public class Request {
|
||||||
|
final HttpServletRequest http_request;
|
||||||
|
final HashMap<String,String> pathParams=new HashMap<>();
|
||||||
|
public Request(HttpServletRequest http_request) {
|
||||||
|
this.http_request = http_request;
|
||||||
|
}
|
||||||
|
public Map<String,String> getPathParams(){
|
||||||
|
return pathParams;
|
||||||
|
}
|
||||||
|
public String getPath() {
|
||||||
|
return http_request.getPathInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVerb() {
|
||||||
|
return http_request.getMethod();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Look for this parameter in pathParan, queryParams and forms.
|
||||||
|
* @param pname
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Object getParam(String pname,Object def){
|
||||||
|
if(pathParams.containsKey(pname)) return pathParams.get(pname);
|
||||||
|
String[] vals=http_request.getParameterValues(pname);
|
||||||
|
if(vals!=null) return vals.length==1?vals[0]:vals;
|
||||||
|
String hdr=getHeader(pname);
|
||||||
|
if(hdr!=null) return hdr;
|
||||||
|
String cook=getCookie(pname,null);
|
||||||
|
if(cook!=null) return cook;
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
public Request setParam(String pname,Object val){
|
||||||
|
if(pathParams.containsKey(pname)){
|
||||||
|
pathParams.put(pname,String.valueOf(Handy.nz(val,"")));
|
||||||
|
}else{
|
||||||
|
throw new IllegalArgumentException("invalid param name:"+pname);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public String getHeader(String key){
|
||||||
|
return http_request.getHeader(key);
|
||||||
|
}
|
||||||
|
public String getCookie(String name,String def){
|
||||||
|
for(Cookie c:http_request.getCookies()){
|
||||||
|
if(name.equalsIgnoreCase(c.getName())) return c.getValue();
|
||||||
|
}
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
private static final String[] HEADERS4IP = {
|
||||||
|
"X-Forwarded-For",
|
||||||
|
"Proxy-Client-IP",
|
||||||
|
"WL-Proxy-Client-IP",
|
||||||
|
"HTTP_X_FORWARDED_FOR",
|
||||||
|
"HTTP_X_FORWARDED",
|
||||||
|
"HTTP_X_CLUSTER_CLIENT_IP",
|
||||||
|
"HTTP_CLIENT_IP",
|
||||||
|
"HTTP_FORWARDED_FOR",
|
||||||
|
"HTTP_FORWARDED",
|
||||||
|
"HTTP_VIA",
|
||||||
|
"REMOTE_ADDR" };
|
||||||
|
public String getRemoteAddress() {
|
||||||
|
for (String header : HEADERS4IP) {
|
||||||
|
String ip = getHeader(header);
|
||||||
|
if(ip==null || ip.length()==0 || "unknown".equalsIgnoreCase(ip)) continue;
|
||||||
|
return ip.contains(",")?ip.split(",",2)[0]:ip;
|
||||||
|
}
|
||||||
|
return http_request.getRemoteAddr();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package com.reliancy.jabba;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
import jakarta.servlet.http.Cookie;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
/**
|
||||||
|
* Our representation of the response.
|
||||||
|
* We usually wrap servlet response with this object and use in write mode.
|
||||||
|
* But we can also create it with no servletresponse then it represents delayed response to be
|
||||||
|
* read out later and written somewhere.
|
||||||
|
*/
|
||||||
|
public class Response {
|
||||||
|
// status codes
|
||||||
|
public static final int HTTP_OK=HttpServletResponse.SC_OK;
|
||||||
|
public static final int HTTP_BAD_REQUEST=HttpServletResponse.SC_BAD_REQUEST;
|
||||||
|
public static final int HTTP_NOT_FOUND=HttpServletResponse.SC_NOT_FOUND;
|
||||||
|
public static final int HTTP_UNAUTHORIZED=HttpServletResponse.SC_UNAUTHORIZED;
|
||||||
|
public static final int HTTP_FORBIDDEN=HttpServletResponse.SC_FORBIDDEN;
|
||||||
|
|
||||||
|
final protected HttpServletResponse http_response;
|
||||||
|
final protected Writer char_response;
|
||||||
|
final protected OutputStream byte_response;
|
||||||
|
protected ResponseEncoder encoder;
|
||||||
|
protected String content_type;
|
||||||
|
protected Integer status;
|
||||||
|
protected final ArrayList<HTTP.Header> headers=new ArrayList<>();
|
||||||
|
protected final ArrayList<HTTP.Cookie> cookies=new ArrayList<>();
|
||||||
|
|
||||||
|
public Response(HttpServletResponse http_response) {
|
||||||
|
this.http_response = http_response;
|
||||||
|
this.char_response=null;
|
||||||
|
this.byte_response=null;
|
||||||
|
}
|
||||||
|
public Response(Writer w) {
|
||||||
|
this.http_response = null;
|
||||||
|
this.char_response=w;
|
||||||
|
this.byte_response=null;
|
||||||
|
}
|
||||||
|
public Response(OutputStream w) {
|
||||||
|
this.http_response = null;
|
||||||
|
this.char_response=null;
|
||||||
|
this.byte_response=w;
|
||||||
|
}
|
||||||
|
public Response() {
|
||||||
|
this.http_response = null;
|
||||||
|
this.char_response=new StringWriter();
|
||||||
|
this.byte_response=null;
|
||||||
|
}
|
||||||
|
public ResponseEncoder getEncoder(){
|
||||||
|
if(encoder==null) encoder=new ResponseEncoder(this);
|
||||||
|
return encoder;
|
||||||
|
}
|
||||||
|
/**returns accumulated string body content if in stringwriter mode or possibly bytearray*/
|
||||||
|
public Object getContent(){
|
||||||
|
if(char_response instanceof StringWriter){
|
||||||
|
return ((StringWriter)char_response).toString();
|
||||||
|
}else if( byte_response instanceof ByteArrayOutputStream){
|
||||||
|
return ((ByteArrayOutputStream)byte_response).toByteArray();
|
||||||
|
}else return null;
|
||||||
|
}
|
||||||
|
/** similar to get content only sends own content to external encoder.
|
||||||
|
* @throws IOException
|
||||||
|
**/
|
||||||
|
public void exportContent(ResponseEncoder ext) throws IOException {
|
||||||
|
if(char_response instanceof StringWriter){
|
||||||
|
ext.writeString(((StringWriter)char_response).toString());
|
||||||
|
}else if( byte_response instanceof ByteArrayOutputStream){
|
||||||
|
byte[] buf=((ByteArrayOutputStream)byte_response).toByteArray();
|
||||||
|
ext.writeBytes(buf,0,buf.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContentType(String ctype) {
|
||||||
|
content_type=ctype;
|
||||||
|
if(http_response!=null) http_response.setContentType(ctype);
|
||||||
|
}
|
||||||
|
public String getContentType(){
|
||||||
|
return content_type;
|
||||||
|
}
|
||||||
|
public void setStatus(int status) {
|
||||||
|
this.status=status;
|
||||||
|
if(http_response!=null) http_response.setStatus(status);
|
||||||
|
}
|
||||||
|
public Integer getStatus(){
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
public String getHeader(String key){
|
||||||
|
for(HTTP.Header hdr:headers){
|
||||||
|
if(key.equalsIgnoreCase(key)) return hdr.value;
|
||||||
|
}
|
||||||
|
if(http_response!=null){
|
||||||
|
return http_response.getHeader(key);
|
||||||
|
}else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Response setHeader(String key,String val){
|
||||||
|
HTTP.Header sel=null;
|
||||||
|
for(HTTP.Header hdr:headers){
|
||||||
|
if(key.equalsIgnoreCase(key)){
|
||||||
|
sel=hdr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(sel!=null) sel.value=val; else headers.add(new HTTP.Header(key,val));
|
||||||
|
if(http_response!=null) http_response.setHeader(key,val);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public List<HTTP.Header> getHeaders(){
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
public String getCookie(String key){
|
||||||
|
for(HTTP.Cookie c:cookies){
|
||||||
|
if(key.equalsIgnoreCase(key)) return c.value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public Response setCookie(String key,String val,int maxAge,boolean secure){
|
||||||
|
HTTP.Cookie sel=null;
|
||||||
|
for(HTTP.Cookie hdr:cookies){
|
||||||
|
if(key.equalsIgnoreCase(key)){
|
||||||
|
sel=hdr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(sel!=null){
|
||||||
|
sel.value=val;
|
||||||
|
sel.maxAge=maxAge;
|
||||||
|
sel.secure=secure;
|
||||||
|
} else{
|
||||||
|
cookies.add(new HTTP.Cookie(key,val,maxAge,secure));
|
||||||
|
}
|
||||||
|
if(http_response!=null){
|
||||||
|
Cookie c=new Cookie(key,val);
|
||||||
|
c.setMaxAge(maxAge);
|
||||||
|
c.setSecure(secure);
|
||||||
|
http_response.addCookie(c);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public List<HTTP.Cookie> getCookies(){
|
||||||
|
return cookies;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
package com.reliancy.jabba;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
public class ResponseEncoder {
|
||||||
|
protected final Response response;
|
||||||
|
protected final Locale locale;
|
||||||
|
protected Writer writer;
|
||||||
|
protected OutputStream out;
|
||||||
|
|
||||||
|
public ResponseEncoder(Response r){
|
||||||
|
response=r;
|
||||||
|
locale=Locale.getDefault();
|
||||||
|
}
|
||||||
|
public ResponseEncoder(Response r,Locale loc){
|
||||||
|
response=r;
|
||||||
|
locale=loc;
|
||||||
|
}
|
||||||
|
protected 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");
|
||||||
|
if(response.http_response!=null){
|
||||||
|
out=response.http_response.getOutputStream();
|
||||||
|
}else if(response.byte_response!=null){
|
||||||
|
out=response.byte_response;
|
||||||
|
}else{
|
||||||
|
out=new ByteArrayOutputStream();
|
||||||
|
}
|
||||||
|
writer=new OutputStreamWriter(out,StandardCharsets.UTF_8);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
protected 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");
|
||||||
|
if(response.http_response!=null){
|
||||||
|
writer=response.http_response.getWriter();
|
||||||
|
}else if(response.char_response!=null){
|
||||||
|
writer=response.char_response;
|
||||||
|
}else if(response.byte_response!=null){
|
||||||
|
out=response.byte_response;
|
||||||
|
writer=new OutputStreamWriter(out,StandardCharsets.UTF_8);
|
||||||
|
}else{
|
||||||
|
writer=new StringWriter();
|
||||||
|
}
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
public ResponseEncoder writeBytes(byte[] buf,int offset,int len) throws IOException{
|
||||||
|
getOutputStream().write(buf,offset, len);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public ResponseEncoder writeString(String str) throws IOException{
|
||||||
|
getWriter().append(str);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public ResponseEncoder writeStream(InputStream is) throws IOException{
|
||||||
|
byte[] buf=new byte[2*4096];
|
||||||
|
int bytesRead=-1;
|
||||||
|
while((bytesRead=is.read(buf))!=-1){
|
||||||
|
writeBytes(buf,0,bytesRead);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public ResponseEncoder writeln(String msg,Object ... args) throws IOException{
|
||||||
|
if(args.length==0){
|
||||||
|
getWriter().append(msg).append("\n");
|
||||||
|
}else{
|
||||||
|
String str=MessageFormat.format(msg,args);
|
||||||
|
getWriter().append(str).append("\n");
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public ResponseEncoder writeIterator(Iterator<String> it) throws IOException{
|
||||||
|
Writer wr=getWriter();
|
||||||
|
while(it.hasNext()) wr.append(it.next());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public ResponseEncoder writeReader(Reader rd) throws IOException{
|
||||||
|
char[] buffer = new char[2*4096];
|
||||||
|
int n = 0;
|
||||||
|
Writer wr=this.getWriter();
|
||||||
|
while (-1 != (n = rd.read(buffer))) {
|
||||||
|
wr.write(buffer, 0, n);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public ResponseEncoder writeObject(Object ret) throws IOException{
|
||||||
|
if(ret==null) return this;
|
||||||
|
Writer wr=getWriter();
|
||||||
|
if(ret instanceof Iterator){
|
||||||
|
Iterator<?> it=(Iterator<?>)ret;
|
||||||
|
while(it.hasNext()){
|
||||||
|
Object obj=it.next();
|
||||||
|
writeObject(obj);
|
||||||
|
}
|
||||||
|
}else if(ret instanceof Collection){
|
||||||
|
Collection<?> cret=(Collection<?>) ret;
|
||||||
|
for(Object o:cret) writeObject(o);
|
||||||
|
}else if(ret instanceof Reader){
|
||||||
|
writeReader((Reader)ret);
|
||||||
|
}else if(ret instanceof byte[]){
|
||||||
|
byte[] bret=(byte[])ret;
|
||||||
|
writeBytes(bret,0,bret.length);
|
||||||
|
}else{
|
||||||
|
wr.append(ret.toString());
|
||||||
|
}
|
||||||
|
//wr.append("\n");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.reliancy.jabba;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Route {
|
||||||
|
String path() default "{method}";
|
||||||
|
String verb() default "GET|POST|DELETE";
|
||||||
|
String return_mime() default "";
|
||||||
|
}
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
package com.reliancy.jabba;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.reliancy.jabbasec.SecurityPolicy;
|
||||||
|
import com.reliancy.util.Template;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.Connector;
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router is entry point and servlet implementation that dispatches messages to our endpoints.
|
||||||
|
* It will launch an embedded jetty server.
|
||||||
|
* It will provide facilities to register endpoints.
|
||||||
|
*/
|
||||||
|
public class Router extends AbstractHandler{
|
||||||
|
protected Connector[] connectors;
|
||||||
|
protected Server jetty;
|
||||||
|
protected Processor first=null;
|
||||||
|
protected Processor last=null;
|
||||||
|
protected RouterEndPoint main=null;
|
||||||
|
protected transient Config config=null;
|
||||||
|
protected Logger logger=LoggerFactory.getLogger(Router.class);
|
||||||
|
|
||||||
|
public Router() {
|
||||||
|
jetty = new Server();
|
||||||
|
jetty.setHandler(this);
|
||||||
|
}
|
||||||
|
public Config getConfig(){
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
public void addProcessor(Processor m){
|
||||||
|
if(first==null){
|
||||||
|
last=first=m;
|
||||||
|
}else{
|
||||||
|
last.next=m;
|
||||||
|
}
|
||||||
|
while(last.next!=null) last=last.next;
|
||||||
|
}
|
||||||
|
public void removeProcessor(Processor m){
|
||||||
|
if(first==m){
|
||||||
|
if(first==last) last=null;
|
||||||
|
first=first.next;
|
||||||
|
while(last!=null && last.next!=null) last=last.next;
|
||||||
|
}else{
|
||||||
|
for(Processor prev=first;prev!=null;prev=prev.next){
|
||||||
|
if(prev.next==m){
|
||||||
|
if(last==m) last=prev;
|
||||||
|
prev.next=m.next;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.next=null;
|
||||||
|
}
|
||||||
|
public Processor getProcessor(String id){
|
||||||
|
for(Processor c=first;c!=null;c=c.next){
|
||||||
|
if(c.getId().equalsIgnoreCase(id)) return c;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RouterEndPoint getMain() {
|
||||||
|
return main;
|
||||||
|
}
|
||||||
|
public void setMain(RouterEndPoint resolver) {
|
||||||
|
this.main = resolver;
|
||||||
|
}
|
||||||
|
public RouterEndPoint importEndPoints(Object target){
|
||||||
|
RouterEndPoint ret=new RouterEndPoint();
|
||||||
|
LinkedList<Method> routes=new LinkedList<>();
|
||||||
|
Class<?> type=target.getClass();
|
||||||
|
while (type != null) {
|
||||||
|
for(Method m : type.getDeclaredMethods()){
|
||||||
|
//System.out.println("Method:"+m.toString());
|
||||||
|
if(m.getAnnotation(Route.class)!=null){
|
||||||
|
routes.add(0,m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type = type.getSuperclass();
|
||||||
|
}
|
||||||
|
for(Method m:routes){
|
||||||
|
//System.out.println("M:"+m);
|
||||||
|
Route r=m.getAnnotation(Route.class);
|
||||||
|
MethodEndPoint mm=new MethodEndPoint(target,m,r);
|
||||||
|
ret.addRoute(r.verb(),mm.getPath(),mm);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handle(String target,
|
||||||
|
Request baseRequest,
|
||||||
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response)
|
||||||
|
throws IOException, ServletException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
com.reliancy.jabba.Request req=new com.reliancy.jabba.Request(request);
|
||||||
|
Response resp=new Response(response);
|
||||||
|
CallSession ss=CallSession.getInstance();
|
||||||
|
try{
|
||||||
|
ss.begin(null, req, resp);
|
||||||
|
if(first!=null) first.process(req, resp);
|
||||||
|
if(main!=null) main.process(req,resp);
|
||||||
|
}finally{
|
||||||
|
ss.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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{
|
||||||
|
if(config!=null) throw new RuntimeException("Router running already");
|
||||||
|
config=conf;
|
||||||
|
for(Processor p=first;p!=null;p=p.getNext()){
|
||||||
|
p.begin(config);
|
||||||
|
}
|
||||||
|
if(main!=null) main.begin(config);
|
||||||
|
jetty.setConnectors(getConnectors());
|
||||||
|
try{
|
||||||
|
jetty.start();
|
||||||
|
}catch(Exception ex){
|
||||||
|
if(ex.getCause() instanceof java.net.BindException){
|
||||||
|
logger.error("Bind issue",ex);
|
||||||
|
Thread.sleep(3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void end() throws Exception{
|
||||||
|
if(main!=null) main.end();
|
||||||
|
for(Processor p=first;p!=null;p=p.getNext()){
|
||||||
|
p.end();
|
||||||
|
}
|
||||||
|
config=null;
|
||||||
|
logger.info("stopiing jetty");
|
||||||
|
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();
|
||||||
|
//System.out.println("cleanup...");
|
||||||
|
System.gc();
|
||||||
|
//System.out.println("return...");
|
||||||
|
}
|
||||||
|
public void run(Config conf) throws Exception {
|
||||||
|
try{
|
||||||
|
begin(conf);
|
||||||
|
//System.out.println("Entering server loop...");
|
||||||
|
jetty.join();
|
||||||
|
}finally{
|
||||||
|
//System.out.println("Exiting server loop...");
|
||||||
|
end();
|
||||||
|
//System.out.println("Exiting server loop...done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static void main( String[] args ) throws Exception
|
||||||
|
{
|
||||||
|
//System.out.println("Hello World!");
|
||||||
|
Router app=new Router();
|
||||||
|
app.addProcessor(new AppSessionFilter());
|
||||||
|
app.addProcessor(new SecurityPolicy());
|
||||||
|
app.setMain(app.importEndPoints(app));
|
||||||
|
FileServer fs=new FileServer("/static","./var");
|
||||||
|
fs.exportRoutes(app.getMain());
|
||||||
|
app.run(null);
|
||||||
|
//System.out.println("Goodbye World!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Route()
|
||||||
|
public String hello(){
|
||||||
|
Map<String, Object> context = new HashMap<>();
|
||||||
|
context.put("name", "Jared");
|
||||||
|
String ret="";
|
||||||
|
try {
|
||||||
|
Template.search_path("./var",SecurityPolicy.class);
|
||||||
|
Template t=Template.find("resources/login.j2");
|
||||||
|
System.out.println("Template:"+t);
|
||||||
|
ret = t.render(context).toString();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
//#return "Hello World";
|
||||||
|
}
|
||||||
|
@Route(
|
||||||
|
path="/helloPlain"
|
||||||
|
)
|
||||||
|
public void hello2(com.reliancy.jabba.Request req,Response resp) throws IOException{
|
||||||
|
resp.getEncoder().writeln("Hi There");
|
||||||
|
}
|
||||||
|
@Route(
|
||||||
|
path="/hello3/{idd:int}"
|
||||||
|
)
|
||||||
|
public String hello3(int id){
|
||||||
|
return "Hello3:"+id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package com.reliancy.jabba;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.reliancy.util.Handy;
|
||||||
|
|
||||||
|
public class RouterEndPoint extends EndPoint{
|
||||||
|
HashMap<String,EndPoint> routes=new HashMap<>();
|
||||||
|
HashMap<String,ArrayList<String>> routeParams=new HashMap<>();
|
||||||
|
ArrayList<String> patterns=new ArrayList<>(); // route patterns ordered
|
||||||
|
int[] indexes; // indexes for each route within regex
|
||||||
|
Pattern regex;
|
||||||
|
|
||||||
|
public RouterEndPoint() {
|
||||||
|
super(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serve(Request req, Response resp) throws IOException {
|
||||||
|
//System.out.println(req.http_request);
|
||||||
|
String verb=req.getVerb();
|
||||||
|
String path=req.getPath();
|
||||||
|
log().info("serving:{}",path);
|
||||||
|
Matcher m=match(verb,path);
|
||||||
|
//Matcher m=rep.match("GET","/helloP");
|
||||||
|
if(m!=null){
|
||||||
|
//HashMap<String,String> pms=new HashMap<>();
|
||||||
|
String rt=evalMatcher(m,req.getPathParams());
|
||||||
|
//System.out.println(rt);
|
||||||
|
//System.out.println(req.getPathParams());
|
||||||
|
EndPoint ep=getRoute(rt);
|
||||||
|
if(ep!=null){
|
||||||
|
ep.process(req, resp);
|
||||||
|
}else{
|
||||||
|
log().error("no endpoint for:{}",rt);
|
||||||
|
resp.setContentType("text/plain;charset=utf-8");
|
||||||
|
resp.setStatus(Response.HTTP_NOT_FOUND);
|
||||||
|
resp.getEncoder().writeln("no endpoint for :"+rt);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
log().error("could not resolve path:{}",path);
|
||||||
|
resp.setContentType("text/plain;charset=utf-8");
|
||||||
|
resp.setStatus(Response.HTTP_NOT_FOUND);
|
||||||
|
resp.getEncoder().writeln("could not resolve path:"+path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public EndPoint getRoute(String r){
|
||||||
|
return routes.get(r);
|
||||||
|
}
|
||||||
|
public void addRoute(String verb,String path, EndPoint mm) {
|
||||||
|
if(verb==null) verb="GET|POST|DELETE";
|
||||||
|
String pathPat=path.replaceAll("\\{(.+)\\}","(.+)");
|
||||||
|
String routePat=Handy.wrap(verb,"(",")")+" "+pathPat;
|
||||||
|
if(!routePat.endsWith("/") && !routePat.endsWith("$")) routePat+="$";
|
||||||
|
routes.put(routePat,mm);
|
||||||
|
//System.out.println("Adding route:"+routePat);
|
||||||
|
ArrayList<String> params=new ArrayList<String>();
|
||||||
|
Pattern p=Pattern.compile("\\{(.+)\\}");
|
||||||
|
Matcher m=p.matcher(path);
|
||||||
|
while(m.find()){
|
||||||
|
String g=m.group();
|
||||||
|
params.add(Handy.unwrap(g,"{","}"));
|
||||||
|
}
|
||||||
|
if(params.isEmpty()==false) routeParams.put(routePat,params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void compile() {
|
||||||
|
patterns.clear();
|
||||||
|
for(String r:routes.keySet()){
|
||||||
|
patterns.add(r);
|
||||||
|
}
|
||||||
|
// sort with longest first
|
||||||
|
Collections.sort(patterns,Comparator.comparing((str)->{return -str.length();}));
|
||||||
|
String fullPat = "("+String.join(")|(",patterns)+")";
|
||||||
|
regex=Pattern.compile(fullPat);
|
||||||
|
// also recompute indexes
|
||||||
|
indexes=new int[patterns.size()];
|
||||||
|
int index=1;
|
||||||
|
for (int i = 0; i < indexes.length; i++) {
|
||||||
|
indexes[i]=index;
|
||||||
|
String p=patterns.get(i);
|
||||||
|
index+=2; // this includes the verb group
|
||||||
|
if(routeParams.containsKey(p)){ // this includes any param groups
|
||||||
|
index+=routeParams.get(p).size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Arrays.stream(indexes).forEach(e->System.out.println(e+" "));
|
||||||
|
}
|
||||||
|
public Matcher match(String verb,String path){
|
||||||
|
if(regex==null) compile();
|
||||||
|
String input=verb+" "+path;
|
||||||
|
Matcher m=regex.matcher(input);
|
||||||
|
if(!m.find()) return null;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Find the route and return also url params.
|
||||||
|
* url params are saved in two ways by name and by pos.
|
||||||
|
* @param m
|
||||||
|
* @param routeParams
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String evalMatcher(Matcher m,Map<String,String> p){
|
||||||
|
String gstr=null;
|
||||||
|
int gindex=1;
|
||||||
|
while(gindex<m.groupCount() && gstr==null){
|
||||||
|
gstr=m.group(gindex);
|
||||||
|
if(gstr==null) gindex++;
|
||||||
|
}
|
||||||
|
if(gstr==null) return null; // no group found
|
||||||
|
// now find route given group
|
||||||
|
int rindex=-1;
|
||||||
|
for (int i = 0; i < indexes.length && rindex<0; i++) {
|
||||||
|
if(gindex==indexes[i]) rindex=i;
|
||||||
|
}
|
||||||
|
if(rindex<0) return null; // we can't match route to group
|
||||||
|
String ret=patterns.get(rindex);
|
||||||
|
if(p!=null && routeParams.containsKey(ret)){
|
||||||
|
ArrayList<String> pms=routeParams.get(ret);
|
||||||
|
for(int i=0;i<pms.size();i++){
|
||||||
|
String val=m.group(gindex+2+i);
|
||||||
|
String byName=pms.get(i).toLowerCase();
|
||||||
|
p.put(byName,val);
|
||||||
|
p.put("_arg"+i,val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.reliancy.jabba;
|
||||||
|
|
||||||
|
/** Session is temporary storage.
|
||||||
|
* we will have application session but also call session.
|
||||||
|
* for methodendpoints we must still be able to access request,response objects.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface Session {
|
||||||
|
public void setValue(String key,Object val);
|
||||||
|
public Object getValue(String key);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.reliancy.jabbasec;
|
||||||
|
|
||||||
|
public class NotAuthentic extends RuntimeException {
|
||||||
|
public NotAuthentic(String message){
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
public NotAuthentic(String message,Throwable cause){
|
||||||
|
super(message,cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.reliancy.jabbasec;
|
||||||
|
/**
|
||||||
|
* Our own exception to throw when we are not allowed access.
|
||||||
|
*/
|
||||||
|
public class NotPermitted extends RuntimeException{
|
||||||
|
public NotPermitted(String message){
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
public NotPermitted(String message,Throwable cause){
|
||||||
|
super(message,cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.reliancy.jabbasec;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any entity that can be secured or permissions set for an actor.
|
||||||
|
*/
|
||||||
|
public interface Securable {
|
||||||
|
public Integer getId();
|
||||||
|
public Integer getOwnerId();
|
||||||
|
public String getKind();
|
||||||
|
public String getName();
|
||||||
|
public String getTitle();
|
||||||
|
public String getIcon();
|
||||||
|
public Securable getOwner();
|
||||||
|
public List<Securable> getOwnedSecurables();
|
||||||
|
public List<SecurityPermit> getDirectPermits();
|
||||||
|
public SecurityPermit getPermit(Securable sec);
|
||||||
|
public SecurityPolicy getPolicy();
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.reliancy.jabbasec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface that is implemented by any User or Principal entity.
|
||||||
|
* Often an AppSession will be determined in many ways by the user.
|
||||||
|
*/
|
||||||
|
public interface SecurityActor extends Securable{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.reliancy.jabbasec;
|
||||||
|
/**
|
||||||
|
* An object describing what rights an actor has on a securable.
|
||||||
|
* This object can be one individual rule or an effective merge of multiple rights.
|
||||||
|
* In any case constructing this class will not be allowed except by security policy.
|
||||||
|
* We should start implementing from security policy and maybe we do not even need this class.
|
||||||
|
*/
|
||||||
|
public interface SecurityPermit {
|
||||||
|
public SecurityActor getActor();
|
||||||
|
public Securable getSubject();
|
||||||
|
public boolean canRead();
|
||||||
|
public boolean canWrite();
|
||||||
|
public boolean canDelete();
|
||||||
|
public boolean canCreate();
|
||||||
|
public boolean canSecure();
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package com.reliancy.jabbasec;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.reliancy.jabba.AppSession;
|
||||||
|
import com.reliancy.jabba.CallSession;
|
||||||
|
import com.reliancy.jabba.Processor;
|
||||||
|
import com.reliancy.jabba.Request;
|
||||||
|
import com.reliancy.jabba.Response;
|
||||||
|
import com.reliancy.util.Handy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SecurityPolicy is a filter/processor that implements various auth protocols but also sources users.
|
||||||
|
* The policy will produce new users once they are authenticated.
|
||||||
|
* SecurityPolicy will authenticate SecurityActors and possibly authorize them via a permission mechanism
|
||||||
|
* to access.
|
||||||
|
* SecurityProtocol is one authenticatio method. We will not adjust response only try to recover user.
|
||||||
|
* Initialization of auth will occur outside:
|
||||||
|
* - for gui after login we will set an auth cookie to remember user and password
|
||||||
|
* - for APIs if user is required will issue error 401 with WWW-Authenticate header
|
||||||
|
*/
|
||||||
|
public class SecurityPolicy extends Processor{
|
||||||
|
public static final String KEY_NAME="jbauth";
|
||||||
|
protected String secret="sdfklgj 7150 9178-54=09";
|
||||||
|
protected ArrayList<SecurityProtocol> protocols;
|
||||||
|
protected SecurityActor admin;
|
||||||
|
protected SecurityActor guest;
|
||||||
|
|
||||||
|
public SecurityPolicy() {
|
||||||
|
super(SecurityPolicy.class.getSimpleName().toLowerCase());
|
||||||
|
protocols=new ArrayList<>();
|
||||||
|
protocols.add(new SecurityProtocol.Digest());
|
||||||
|
protocols.add(new SecurityProtocol.Basic());
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void before(Request request, Response response) throws IOException {
|
||||||
|
// we will recover a user here
|
||||||
|
CallSession css=CallSession.getInstance();
|
||||||
|
AppSession ass=(AppSession) css.getAppSession();
|
||||||
|
if(ass==null || ass.getUser()!=null){
|
||||||
|
return; // we got a user all good
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
SecurityActor user=authenticate(request);
|
||||||
|
if(user!=null) ass.setUser(user);
|
||||||
|
}catch(NotAuthentic ex){
|
||||||
|
// we could not establish user
|
||||||
|
response.setStatus(Response.HTTP_FORBIDDEN);
|
||||||
|
response.getEncoder().writeObject(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void after(Request request, Response response) throws IOException {
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void serve(Request request, Response response) throws IOException {
|
||||||
|
// nothing to do here
|
||||||
|
}
|
||||||
|
protected String getSecret(){
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
/** authenticates or establishes user based on request and updates response.
|
||||||
|
* this method might redirect to a login view. once it is done
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @return user we could establish
|
||||||
|
* @throws NotPermitted
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public SecurityActor authenticate(Request req) throws IOException, NotAuthentic{
|
||||||
|
// must recover user from cookies or by redirecting to login
|
||||||
|
log().info("User is missing.");
|
||||||
|
String auth=req.getCookie(KEY_NAME,null);
|
||||||
|
if(auth!=null){
|
||||||
|
// we have an auth cookie - encoded user login
|
||||||
|
Map<String,String> kv=Handy.decrypt(getSecret(),auth);
|
||||||
|
String username=(kv.get("n"));
|
||||||
|
String password=(kv.get("p"));
|
||||||
|
String address=(kv.get("a"));
|
||||||
|
if(address!=null && !address.equals(req.getRemoteAddress())){
|
||||||
|
return null; // invalid auth cookie
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
SecurityActor user = loadActor(admin,username,password);
|
||||||
|
if(user!=null) return user;
|
||||||
|
else throw new NotAuthentic("invalid credentials");
|
||||||
|
} catch (NotPermitted e) {
|
||||||
|
throw new NotAuthentic("not permitted to authenticate",e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// try authorization header as fallback - can't clear it always
|
||||||
|
auth=req.getHeader("Authorization");
|
||||||
|
if(auth!=null){
|
||||||
|
String[] kv=auth.split(" ",2);
|
||||||
|
String proto_name=kv[0];
|
||||||
|
String proto_args=kv.length>1?kv[1]:"";
|
||||||
|
for(SecurityProtocol sproto:protocols){
|
||||||
|
if(proto_name.equalsIgnoreCase(sproto.getName())){
|
||||||
|
return sproto.authenticate(this, req,proto_args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new NotAuthentic("auth method not supported:"+proto_name);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
/** will establish what if any rights an actor has on a securable. */
|
||||||
|
public SecurityPermit authorize(SecurityActor actor, Securable subject){
|
||||||
|
return actor.getPermit(subject);
|
||||||
|
}
|
||||||
|
/** loads a securable by id given actor permits. */
|
||||||
|
public Securable loadSecurable(SecurityActor actor, Integer id) throws IOException, NotPermitted{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
/** loads an actor given name and password. */
|
||||||
|
public SecurityActor loadActor(SecurityActor actor, String name, String pwd) throws IOException, NotPermitted{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
/** will save a securable including a user if permitted via actor. */
|
||||||
|
public void saveSecurable(SecurityActor actor, Securable sec) throws IOException{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.reliancy.jabbasec;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.reliancy.jabba.Request;
|
||||||
|
import com.reliancy.util.Handy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A SecurityProtocol will be processing HTTP to establish SecurityActor or user.
|
||||||
|
*/
|
||||||
|
public abstract class SecurityProtocol {
|
||||||
|
protected final String name;
|
||||||
|
public SecurityProtocol(String n){
|
||||||
|
name=n;
|
||||||
|
}
|
||||||
|
public String getName(){
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
public abstract SecurityActor authenticate(SecurityPolicy policy,Request req,String tok) throws NotPermitted, IOException ;
|
||||||
|
public static class Basic extends SecurityProtocol{
|
||||||
|
public Basic(){
|
||||||
|
super("Basic");
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public SecurityActor authenticate(SecurityPolicy policy,Request req,String tok) throws NotPermitted, IOException {
|
||||||
|
tok=String.valueOf(Handy.decodeBase64(tok)) ;
|
||||||
|
String[] up=tok.split(":",2);
|
||||||
|
String userid=up[0];
|
||||||
|
String pwd=up.length>1?up[1]:"";
|
||||||
|
return policy.loadActor(policy.admin,userid,pwd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static class Digest extends SecurityProtocol{
|
||||||
|
public Digest(){
|
||||||
|
super("Digest");
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public SecurityActor authenticate(SecurityPolicy policy,Request req,String tok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{% extends "base.j2" %}
|
||||||
|
{% block content %}
|
||||||
|
<div>Hello from code: {{name}}!</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.reliancy.rec;
|
||||||
|
|
||||||
|
/** Similar to a SAX interface used by parsers for XML and JSON to assemble DOM structures.
|
||||||
|
* Simply gets notified of events during parsing.
|
||||||
|
* @author amer
|
||||||
|
*/
|
||||||
|
public interface DecoderSink {
|
||||||
|
void beginDocument(Rec init);
|
||||||
|
Rec endDocument();
|
||||||
|
void beginElement(String name);
|
||||||
|
void endElement(String name);
|
||||||
|
void setKey(String name);
|
||||||
|
void setValue(CharSequence seq);
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package com.reliancy.rec;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
|
||||||
|
/** Base class of meta objects.
|
||||||
|
* We use it to describe certain meta information. We derive from it Slot.
|
||||||
|
* We define keys list of slots on the header level to describe slots.
|
||||||
|
*/
|
||||||
|
public class Hdr {
|
||||||
|
public static final int FLAG_ARRAY =0x0001;
|
||||||
|
public static final int FLAG_CHANGED =0x0002;
|
||||||
|
public static final int FLAG_HIDDEN =0x0004;
|
||||||
|
public static final int FLAG_LOCKED =0x0008;
|
||||||
|
int flags;
|
||||||
|
String name;
|
||||||
|
String label;
|
||||||
|
Class<?> type;
|
||||||
|
final ArrayList<Slot> keys;
|
||||||
|
|
||||||
|
public Hdr(String name) {
|
||||||
|
this.name=name;
|
||||||
|
keys=new ArrayList<>();
|
||||||
|
}
|
||||||
|
public Hdr(String name,Class<?> type) {
|
||||||
|
this.name=name;
|
||||||
|
this.type=type;
|
||||||
|
keys=new ArrayList<>();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String toString(){
|
||||||
|
StringBuilder ret=new StringBuilder();
|
||||||
|
ret.append("{").append("flags:").append(flags).append(",name:").append(name);
|
||||||
|
ret.append(",dim:").append(keys.size()).append("}");
|
||||||
|
return ret.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
public String getLabel() {
|
||||||
|
return label!=null?label:name;
|
||||||
|
}
|
||||||
|
public void setLabel(String name) {
|
||||||
|
this.label = name;
|
||||||
|
}
|
||||||
|
public Class<?> getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
public void setType(Class<?> type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
public Hdr raiseFlags(int f){
|
||||||
|
flags|=f;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Hdr clearFlags(int f){
|
||||||
|
flags&=~f;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public boolean checkFlags(int f){
|
||||||
|
return (flags & f)!=0;
|
||||||
|
}
|
||||||
|
public <T extends Hdr> T castAs(Class<T> clazz){
|
||||||
|
return clazz.cast(this);
|
||||||
|
}
|
||||||
|
public int findSlot(String name){
|
||||||
|
return findSlot(name,0);
|
||||||
|
}
|
||||||
|
public int findSlot(String name,int ofs){
|
||||||
|
ListIterator<Slot> it=keys.listIterator(ofs);
|
||||||
|
while(it.hasNext()){
|
||||||
|
int index=it.nextIndex();
|
||||||
|
Slot e=it.next();
|
||||||
|
if(e.getName().equalsIgnoreCase(name)) return index;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
public int findSlot(Slot s,int ofs){
|
||||||
|
ListIterator<Slot> it=keys.listIterator(ofs);
|
||||||
|
while(it.hasNext()){
|
||||||
|
int index=it.nextIndex();
|
||||||
|
Slot e=it.next();
|
||||||
|
if(e==s) return index;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* this version will get or create a slot by given name.
|
||||||
|
* @param name
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Slot getSlot(String name){
|
||||||
|
int index=findSlot(name);
|
||||||
|
if(index<0){
|
||||||
|
return new Slot(name);
|
||||||
|
}else{
|
||||||
|
return getSlot(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Slot getSlot(int pos){
|
||||||
|
return keys.get(pos);
|
||||||
|
}
|
||||||
|
public Hdr removeSlot(int pos){
|
||||||
|
keys.remove(pos);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Hdr addSlot(Slot s){
|
||||||
|
keys.add(s);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Hdr setSlot(int index,Slot s){
|
||||||
|
keys.set(index,s);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Slot[] slots(Slot... slots){
|
||||||
|
if(slots!=null && slots.length>0){
|
||||||
|
keys.clear();
|
||||||
|
for(int i=0;i<slots.length;i++) keys.add(slots[i]);
|
||||||
|
}
|
||||||
|
return keys.toArray(new Slot[keys.size()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.reliancy.rec;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static methods related to JSON format.
|
||||||
|
*/
|
||||||
|
public class JSON {
|
||||||
|
private JSON(){
|
||||||
|
}
|
||||||
|
public static final Rec reads(CharSequence seq){
|
||||||
|
JSONDecoder dec=new JSONDecoder();
|
||||||
|
dec.beginDocument();
|
||||||
|
dec.parse(0, seq);
|
||||||
|
return dec.endDocument();
|
||||||
|
}
|
||||||
|
public static final void writes(Rec rec,Appendable sink) throws IOException{
|
||||||
|
JSONEncoder.encode(rec, sink);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,331 @@
|
|||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.reliancy.rec;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import com.reliancy.util.Tokenizer;
|
||||||
|
import com.reliancy.util.Handy;
|
||||||
|
|
||||||
|
/** Special class which will tokenize string according to rules for JSON and feed the info to a listener.
|
||||||
|
*
|
||||||
|
* @author amer
|
||||||
|
*/
|
||||||
|
public class JSONDecoder implements TextDecoder,DecoderSink {
|
||||||
|
DecoderSink handler;
|
||||||
|
String[] inBody;
|
||||||
|
String[] sets;
|
||||||
|
String lastToken=null;
|
||||||
|
StringBuilder out = new StringBuilder();
|
||||||
|
public JSONDecoder(DecoderSink h){
|
||||||
|
handler=h;
|
||||||
|
String delimChars="{}[],;:=";
|
||||||
|
String escapeChars="'\"";
|
||||||
|
String whiteChars=" \t\r\f\n";//" \t\r\f\n";
|
||||||
|
inBody = new String[]{delimChars,escapeChars,whiteChars};
|
||||||
|
sets=inBody;
|
||||||
|
}
|
||||||
|
public JSONDecoder(){
|
||||||
|
this(null);
|
||||||
|
handler=this;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int parse(int offset,CharSequence in){
|
||||||
|
int noffset=0;
|
||||||
|
while((noffset = Tokenizer.nextToken(offset, in, out, sets))!=offset){
|
||||||
|
offset=noffset;
|
||||||
|
if(out.length()==0) continue;
|
||||||
|
String token=out.toString();
|
||||||
|
out.setLength(0);
|
||||||
|
if("{".equals(token)){
|
||||||
|
if(lastToken!=null){
|
||||||
|
if(lastToken.startsWith("/*") || lastToken.startsWith("//")){
|
||||||
|
handler.setValue(lastToken); // support comments in our stream
|
||||||
|
}else{
|
||||||
|
handler.setKey(lastToken); // we consider string before { a key or name unless comment
|
||||||
|
}
|
||||||
|
lastToken=null;
|
||||||
|
}
|
||||||
|
handler.beginElement("object");
|
||||||
|
}else if("}".equals(token)){
|
||||||
|
if(lastToken!=null){
|
||||||
|
handler.setValue(lastToken);
|
||||||
|
lastToken=null;
|
||||||
|
}
|
||||||
|
handler.endElement("object");
|
||||||
|
}else if("[".equals(token)){
|
||||||
|
if(lastToken!=null){
|
||||||
|
handler.setValue(lastToken);
|
||||||
|
lastToken=null;
|
||||||
|
}
|
||||||
|
handler.beginElement("array");
|
||||||
|
}else if("]".equals(token)){
|
||||||
|
if(lastToken!=null){
|
||||||
|
handler.setValue(lastToken);
|
||||||
|
lastToken=null;
|
||||||
|
}
|
||||||
|
handler.endElement("array");
|
||||||
|
}else if(",".equals(token) || ";".equals(token)){
|
||||||
|
if(lastToken!=null){
|
||||||
|
handler.setValue(lastToken);
|
||||||
|
lastToken=null;
|
||||||
|
}
|
||||||
|
}else if(":".equals(token) || "=".equals(token)){
|
||||||
|
if(lastToken!=null){
|
||||||
|
handler.setKey(lastToken);
|
||||||
|
lastToken=null;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
lastToken=token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(lastToken!=null){
|
||||||
|
handler.setValue(lastToken);
|
||||||
|
lastToken=null;
|
||||||
|
}
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
Slot KEY=new Slot("__key",String.class);
|
||||||
|
/** We use a stack structure to manage recusion. */
|
||||||
|
LinkedList<Rec> stack=new LinkedList<Rec>();
|
||||||
|
/** will not add white space only nodes. */
|
||||||
|
boolean whitespaceIgnored=true;
|
||||||
|
boolean entitycharsIgnored=false;
|
||||||
|
|
||||||
|
public boolean isWhitespaceIgnored() {
|
||||||
|
return whitespaceIgnored;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWhitespaceIgnored(boolean whitespaceIgnored) {
|
||||||
|
this.whitespaceIgnored = whitespaceIgnored;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEntitycharsIgnored() {
|
||||||
|
return entitycharsIgnored;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntitycharsIgnored(boolean entitycharsIgnored) {
|
||||||
|
this.entitycharsIgnored = entitycharsIgnored;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rec getRoot() {
|
||||||
|
return stack.getLast();
|
||||||
|
}
|
||||||
|
public Rec getSubject(){
|
||||||
|
if(stack.isEmpty()) return null;
|
||||||
|
return stack.getFirst();
|
||||||
|
}
|
||||||
|
public void pushSubject(Rec n){
|
||||||
|
stack.push(n);
|
||||||
|
}
|
||||||
|
public Rec popSubject(){
|
||||||
|
Rec child=stack.pop();
|
||||||
|
Rec parent=getSubject();
|
||||||
|
if(parent==null) return child;
|
||||||
|
if(parent.isArray()){
|
||||||
|
parent.add(child);
|
||||||
|
}else{
|
||||||
|
String key=(String) parent.get(KEY,null);
|
||||||
|
Slot keyslot=parent.getSlot(key);
|
||||||
|
parent.remove(KEY).set(keyslot,child);
|
||||||
|
// if array and has key it should bomb
|
||||||
|
//parent.setArray(false);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void beginDocument() {
|
||||||
|
beginDocument(null);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void beginDocument(Rec init) {
|
||||||
|
sets=inBody;
|
||||||
|
out.setLength(0);
|
||||||
|
lastToken=null;
|
||||||
|
stack.clear();
|
||||||
|
Rec arr=new Obj(true);
|
||||||
|
stack.push(arr);
|
||||||
|
//System.out.println("BeginDoc");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Rec endDocument() {
|
||||||
|
// need to set the actual parent
|
||||||
|
while(stack.getFirst()!=stack.getLast()){
|
||||||
|
popSubject();
|
||||||
|
}
|
||||||
|
// now adjust the root if it is array with only one child - one we added in start document as first element
|
||||||
|
Rec root=getSubject();
|
||||||
|
if(root.isArray() && root.count()==1 && root.get(0) instanceof Rec){
|
||||||
|
// ok we collapse our array from above - since we only have one object
|
||||||
|
Object bb=root.get(0);
|
||||||
|
Rec b=(Rec)bb ;
|
||||||
|
popSubject();
|
||||||
|
pushSubject(b);
|
||||||
|
}
|
||||||
|
//System.out.println("EndDoc");
|
||||||
|
return getRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beginElement(String name) {
|
||||||
|
Rec element=new Obj("array".equals(name));
|
||||||
|
//element.setAttr(0);
|
||||||
|
pushSubject(element);
|
||||||
|
//System.out.println("BeginElement:"+name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void endElement(String name) {
|
||||||
|
// check if the correct end element is sent
|
||||||
|
Rec sub=this.getSubject();
|
||||||
|
if(!sub.isArray()) sub.remove(KEY);
|
||||||
|
// finally pop the root
|
||||||
|
popSubject();
|
||||||
|
//System.out.println("EndElement:"+name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setKey(String name) {
|
||||||
|
Rec sub=this.getSubject();
|
||||||
|
String key=(String) sub.get(KEY,null);
|
||||||
|
if(key!=null){
|
||||||
|
// something is wrong - our tokizer might have ignored escape char or input has forgotten a delimiter
|
||||||
|
// we try to split name because it would contain key and value merged
|
||||||
|
int split=0;
|
||||||
|
if(name.startsWith("\"")) split=name.indexOf('\"', 1);
|
||||||
|
if(name.startsWith("'")) split=name.indexOf('\'', 1);
|
||||||
|
String val=name.substring(0,split+1);
|
||||||
|
setValue(val);
|
||||||
|
name=name.substring(split+1);
|
||||||
|
}
|
||||||
|
int start=0;int stop=name.length();
|
||||||
|
while(start<stop && (name.charAt(start)=='"' || name.charAt(start)=='\'')) start++;
|
||||||
|
while(start<stop && (name.charAt(stop-1)=='"' || name.charAt(stop-1)=='\'')) stop--;
|
||||||
|
sub.set(KEY, name.subSequence(start, stop));
|
||||||
|
//System.out.println("BeginAttribute:"+name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(CharSequence seq) {
|
||||||
|
if(seq==null) return;
|
||||||
|
Rec sub=this.getSubject();
|
||||||
|
String key=(String) sub.get(KEY,null);
|
||||||
|
if(key==null){
|
||||||
|
if(isWhitespaceIgnored() && Handy.isEmpty(seq)){
|
||||||
|
// skip empty strings
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// now key we are adding to body
|
||||||
|
Object val=interpretString(seq);
|
||||||
|
sub.add(val);
|
||||||
|
}else{
|
||||||
|
// we are setting attribute
|
||||||
|
Object val=interpretString(seq);
|
||||||
|
Slot keyslot=sub.getSlot(key);
|
||||||
|
sub.remove(KEY).set(keyslot,val);
|
||||||
|
// it should bomb if array and comes with key
|
||||||
|
//sub.setArray(false); // if it needs to be array why does it have a key
|
||||||
|
}
|
||||||
|
//System.out.println("Data:"+seq);
|
||||||
|
}
|
||||||
|
public Object interpretString(CharSequence seq){
|
||||||
|
int start=0;int stop=seq.length();
|
||||||
|
while(start<stop && seq.charAt(start)=='"' && seq.charAt(stop-1)=='"'){
|
||||||
|
start++;
|
||||||
|
stop--;
|
||||||
|
}
|
||||||
|
if(start==0 && stop==seq.length()){
|
||||||
|
// we do not trim single quotes unless double are missing
|
||||||
|
while(start<stop && seq.charAt(start)=='\'' && seq.charAt(stop-1)=='\''){
|
||||||
|
start++;
|
||||||
|
stop--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seq=seq.subSequence(start, stop);
|
||||||
|
Object val=seq;
|
||||||
|
if(start==0){
|
||||||
|
String sVal=String.valueOf(seq);
|
||||||
|
// we did not have quotes - so try to interpet a few things
|
||||||
|
if("null".equalsIgnoreCase(sVal)){
|
||||||
|
val=null;
|
||||||
|
}else
|
||||||
|
if("true".equalsIgnoreCase(sVal)){
|
||||||
|
val=Boolean.TRUE;
|
||||||
|
}else
|
||||||
|
if("false".equalsIgnoreCase(sVal)){
|
||||||
|
val=Boolean.FALSE;
|
||||||
|
}else
|
||||||
|
if(Handy.isNumeric(sVal)){
|
||||||
|
if (sVal.indexOf(".") >= 0) {
|
||||||
|
val = Double.parseDouble(sVal);
|
||||||
|
} else {
|
||||||
|
val = Integer.parseInt(sVal);
|
||||||
|
}
|
||||||
|
}else if(this.isEntitycharsIgnored()==false && seq!=null && seq.length()>0){
|
||||||
|
// maybe it is a string after all
|
||||||
|
val=unescape(seq);
|
||||||
|
}
|
||||||
|
}else if(this.isEntitycharsIgnored()==false && seq!=null && seq.length()>0){
|
||||||
|
// we had quotes so lets decode escaed chars
|
||||||
|
val=unescape(seq);
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CharSequence unescape(CharSequence str) {
|
||||||
|
StringBuilder buf = null;
|
||||||
|
for (int i = 0; i < str.length(); i++) {
|
||||||
|
char ch = str.charAt(i);
|
||||||
|
if (ch == '\\' && i < (str.length() - 1)) {
|
||||||
|
i = i + 1;
|
||||||
|
char ch2 = str.charAt(i);
|
||||||
|
switch (ch2) {
|
||||||
|
case '"':
|
||||||
|
if(buf==null) buf=new StringBuilder(i>0?str.subSequence(0, i-1):"");
|
||||||
|
buf.append("\"");
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
if(buf==null) buf=new StringBuilder(i>0?str.subSequence(0, i-1):"");
|
||||||
|
buf.append("\\");
|
||||||
|
break;
|
||||||
|
case '/':
|
||||||
|
if(buf==null) buf=new StringBuilder(i>0?str.subSequence(0, i-1):"");
|
||||||
|
buf.append("/");
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
if(buf==null) buf=new StringBuilder(i>0?str.subSequence(0, i-1):"");
|
||||||
|
buf.append("\b");
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
if(buf==null) buf=new StringBuilder(i>0?str.subSequence(0, i-1):"");
|
||||||
|
buf.append("\f");
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
if(buf==null) buf=new StringBuilder(i>0?str.subSequence(0, i-1):"");
|
||||||
|
buf.append("\n");
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
if(buf==null) buf=new StringBuilder(i>0?str.subSequence(0, i-1):"");
|
||||||
|
buf.append("\r");
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
if(buf==null) buf=new StringBuilder(i>0?str.subSequence(0, i-1):"");
|
||||||
|
buf.append("\t");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if(buf!=null) buf.append(ch);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(buf!=null) buf.append(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf!=null?buf.toString():str;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
package com.reliancy.rec;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class JSONEncoder{
|
||||||
|
public JSONEncoder(){
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* We encode into an appendable various primitives and Rec.
|
||||||
|
* If appendable null then we just compute expected size.
|
||||||
|
* keys are not escaped they better not contain any special chars.
|
||||||
|
* values are quoted and escaped unless we detect a string that looks like a json object those are passed thru.
|
||||||
|
* in the past we tried to deduce if quoting was needed, but this is not the place to do so because we do not know how many times
|
||||||
|
* value was escaped so the only thing we can assume is that it needs to be escaped. So feeding a value that is quoted and
|
||||||
|
* escaped will return back on parse the same and will need to dequoted and descaped once more but that shoudl work fine with
|
||||||
|
* whoever quoted it in the upstream in the first place.
|
||||||
|
* @param val property value
|
||||||
|
* @param o encoding output
|
||||||
|
* @return length in characters of encoded result
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static int encode(Object val,Appendable o) throws IOException {
|
||||||
|
int len = 0;
|
||||||
|
/*
|
||||||
|
// first key
|
||||||
|
if (key != null) {
|
||||||
|
if (o != null) {
|
||||||
|
o.append('"').append(key).append("\":");
|
||||||
|
}
|
||||||
|
len += 3 + key.length();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// now value
|
||||||
|
if (val instanceof Object[]) {
|
||||||
|
Object[] valval = (Object[]) val;
|
||||||
|
if (o != null) {
|
||||||
|
o.append('[');
|
||||||
|
}
|
||||||
|
int index = 0;
|
||||||
|
for (Object obj : valval) {
|
||||||
|
if (index++ > 0) {
|
||||||
|
len += 1;
|
||||||
|
if (o != null) {
|
||||||
|
o.append(",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
len += encode(obj, o);
|
||||||
|
}
|
||||||
|
if (o != null) {
|
||||||
|
o.append(']');
|
||||||
|
}
|
||||||
|
len += 2;
|
||||||
|
} else if (val instanceof List) {
|
||||||
|
List<?> valval = (List<?>) val;
|
||||||
|
if (o != null) {
|
||||||
|
o.append('[');
|
||||||
|
}
|
||||||
|
int index = 0;
|
||||||
|
for (Object obj : valval) {
|
||||||
|
if (index++ > 0) {
|
||||||
|
len += 1;
|
||||||
|
if (o != null) {
|
||||||
|
o.append(",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
len += encode(obj, o);
|
||||||
|
}
|
||||||
|
if (o != null) {
|
||||||
|
o.append(']');
|
||||||
|
}
|
||||||
|
len += 2;
|
||||||
|
} else if (val instanceof Map) {
|
||||||
|
len+=encodeMap((Map<?,?>)val,o);
|
||||||
|
} else if (val instanceof Rec) {
|
||||||
|
len += encodeRec((Rec) val, o);
|
||||||
|
} else if (val instanceof Number || val instanceof Boolean) {
|
||||||
|
String str = val.toString();
|
||||||
|
if (o != null) {
|
||||||
|
o.append(str);
|
||||||
|
}
|
||||||
|
len += str.length();
|
||||||
|
}else if(val instanceof int[]){
|
||||||
|
int[] valval = (int[]) val;
|
||||||
|
if (o != null) {
|
||||||
|
o.append('[');
|
||||||
|
}
|
||||||
|
int index = 0;
|
||||||
|
for (int obj : valval) {
|
||||||
|
if (index++ > 0) {
|
||||||
|
len += 1;
|
||||||
|
if (o != null) {
|
||||||
|
o.append(",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(o!=null) o.append(String.valueOf(obj));
|
||||||
|
len += 1;
|
||||||
|
}
|
||||||
|
if (o != null) {
|
||||||
|
o.append(']');
|
||||||
|
}
|
||||||
|
len += 2;
|
||||||
|
}else if(val instanceof float[]){
|
||||||
|
float[] valval = (float[]) val;
|
||||||
|
if (o != null) {
|
||||||
|
o.append('[');
|
||||||
|
}
|
||||||
|
int index = 0;
|
||||||
|
for (float obj : valval) {
|
||||||
|
if (index++ > 0) {
|
||||||
|
len += 1;
|
||||||
|
if (o != null) {
|
||||||
|
o.append(",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(o!=null) o.append(String.valueOf(obj));
|
||||||
|
len += 1;
|
||||||
|
}
|
||||||
|
if (o != null) {
|
||||||
|
o.append(']');
|
||||||
|
}
|
||||||
|
len += 2;
|
||||||
|
}else if (val instanceof Object) {
|
||||||
|
String str = val.toString();
|
||||||
|
boolean jsontxt = false;
|
||||||
|
jsontxt |= str.length() > 0 && str.startsWith("{") && str.endsWith("}");
|
||||||
|
jsontxt |= str.length() > 0 && str.startsWith("[") && str.endsWith("]");
|
||||||
|
//boolean quoted=str.length() > 1 && str.startsWith("\"") && str.endsWith("\"");
|
||||||
|
// embedded json is not quoted and not escaped
|
||||||
|
// all other text is quoted otherwise we will prevent quoted quotes (those would be swallowed)
|
||||||
|
// we will not try to be smart if someone added an item that is quoted already it will be escaped and queotes retained
|
||||||
|
// we must be consistent so that repeated parse and encode works and not too smart here
|
||||||
|
// we need to put quotes around unless
|
||||||
|
if (!jsontxt) {
|
||||||
|
str = escape(str);
|
||||||
|
if (o != null) {
|
||||||
|
o.append('"');
|
||||||
|
}
|
||||||
|
len += 1;
|
||||||
|
}
|
||||||
|
if (o != null) {
|
||||||
|
o.append(str);
|
||||||
|
}
|
||||||
|
len += str.length();
|
||||||
|
if (!jsontxt) {
|
||||||
|
if (o != null) {
|
||||||
|
o.append('"');
|
||||||
|
}
|
||||||
|
len += 1;
|
||||||
|
}
|
||||||
|
} else if (val == null) {
|
||||||
|
String str = "null";
|
||||||
|
if (o != null) {
|
||||||
|
o.append(str);
|
||||||
|
}
|
||||||
|
len += str.length();
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
public static int encodeMap(Map<?,?> valval,Appendable o) throws IOException{
|
||||||
|
int len=0;
|
||||||
|
if (o != null) {
|
||||||
|
o.append('{');
|
||||||
|
}
|
||||||
|
int index = 0;
|
||||||
|
for (Object obj : valval.keySet()) {
|
||||||
|
if (index++ > 0) {
|
||||||
|
len += 1;
|
||||||
|
if (o != null) {
|
||||||
|
o.append(",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String key=obj.toString();
|
||||||
|
if (o != null) {
|
||||||
|
o.append('"').append(key).append("\":");
|
||||||
|
}
|
||||||
|
len += 3 + key.length();
|
||||||
|
len += encode(valval.get(obj), o);
|
||||||
|
}
|
||||||
|
if (o != null) {
|
||||||
|
o.append('}');
|
||||||
|
}
|
||||||
|
len += 2;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
public static int encodeRec(Rec val,Appendable o) throws IOException{
|
||||||
|
int len=0;
|
||||||
|
if (o != null) {
|
||||||
|
o.append(val.isArray()?"[":"{");
|
||||||
|
}
|
||||||
|
for (int i=0;i<val.count();i++) {
|
||||||
|
Slot k=val.getSlot(i);
|
||||||
|
Object v=val.get(i);
|
||||||
|
if (i > 0) {
|
||||||
|
len += 1;
|
||||||
|
if (o != null) {
|
||||||
|
o.append(",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(k!=null){
|
||||||
|
String key=k.getName();
|
||||||
|
if (o != null) {
|
||||||
|
o.append('"').append(key).append("\":");
|
||||||
|
}
|
||||||
|
len += 3 + key.length();
|
||||||
|
}
|
||||||
|
len += encode(v, o);
|
||||||
|
}
|
||||||
|
if (o != null) {
|
||||||
|
o.append(val.isArray()?"]":"}");
|
||||||
|
}
|
||||||
|
len += 2;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param str
|
||||||
|
* @return true if the string includes any of the special chars.
|
||||||
|
*/
|
||||||
|
public static boolean needsEscaping(String str) {
|
||||||
|
for (int i = 0; i < str.length(); i++) {
|
||||||
|
char ch = str.charAt(i);
|
||||||
|
switch (ch) {
|
||||||
|
case '"':
|
||||||
|
case '\\':
|
||||||
|
case '/':
|
||||||
|
case '\b':
|
||||||
|
case '\f':
|
||||||
|
case '\n':
|
||||||
|
case '\r':
|
||||||
|
case '\t':
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this helper method handle quotes and control chars.
|
||||||
|
* @param str input string
|
||||||
|
* @return output after encoding special chars
|
||||||
|
*/
|
||||||
|
public static String escape(String str) {
|
||||||
|
StringBuilder buf = null;
|
||||||
|
for (int i = 0; i < str.length(); i++) {
|
||||||
|
char ch = str.charAt(i);
|
||||||
|
switch (ch) {
|
||||||
|
case '"':
|
||||||
|
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||||
|
buf.append("\\\"");
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||||
|
buf.append("\\\\");
|
||||||
|
break;
|
||||||
|
case '/':
|
||||||
|
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||||
|
buf.append("\\/");
|
||||||
|
break;
|
||||||
|
case '\b':
|
||||||
|
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||||
|
buf.append("\\b");
|
||||||
|
break;
|
||||||
|
case '\f':
|
||||||
|
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||||
|
buf.append("\\f");
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||||
|
buf.append("\\n");
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||||
|
buf.append("\\r");
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||||
|
buf.append("\\t");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if(buf!=null) buf.append(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf!=null?buf.toString():str;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package com.reliancy.rec;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation of a Rec.
|
||||||
|
* We separate keys and values because Obj could just be an array.
|
||||||
|
* If object is declated an array keys are nonexistant and rec related methods will return null or crash.
|
||||||
|
* Our setters return this object to main the calls chainable.
|
||||||
|
* Also positional calls accept negative values which reference from end backward.
|
||||||
|
*/
|
||||||
|
public class Obj implements Rec{
|
||||||
|
final List<Object> values;
|
||||||
|
final Hdr meta;
|
||||||
|
|
||||||
|
public Obj() {
|
||||||
|
values=new ArrayList<>();
|
||||||
|
meta=new Slot(null);
|
||||||
|
}
|
||||||
|
public Obj(boolean is_array) {
|
||||||
|
values=new ArrayList<>();
|
||||||
|
meta=new Slot(null);
|
||||||
|
if(is_array) meta.raiseFlags(Hdr.FLAG_ARRAY);
|
||||||
|
}
|
||||||
|
public Obj(List<Slot> k,List<Object> v) {
|
||||||
|
values=v;
|
||||||
|
meta=new Slot(null);
|
||||||
|
meta.keys.addAll(k);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This ctor is reserved for derivations with fixed slot definitions.
|
||||||
|
* This constructor will inspect static Slot members and construct keys that way
|
||||||
|
* if meta named.
|
||||||
|
* @param def
|
||||||
|
*/
|
||||||
|
protected Obj(Hdr def){
|
||||||
|
values=new ArrayList<>();
|
||||||
|
meta=def;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String toString(){
|
||||||
|
StringBuilder buf=new StringBuilder();
|
||||||
|
toString(buf);
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
public int toString(StringBuilder buf){
|
||||||
|
boolean is_arr=isArray();
|
||||||
|
int length0=buf.length();// length before anything done
|
||||||
|
//StringBuffer indent=new StringBuffer(); // detect indent
|
||||||
|
//for(int i=length0;i>0 && Character.isWhitespace(buf.charAt(i));i--){
|
||||||
|
// indent.append(buf.codePointAt(i));
|
||||||
|
//}
|
||||||
|
buf.append(is_arr?"[":"{");
|
||||||
|
if(is_arr){
|
||||||
|
for(int pos=0;pos<count();pos++){
|
||||||
|
if(pos>0) buf.append(",");
|
||||||
|
Object val=this.get(pos);
|
||||||
|
if(val instanceof Obj) ((Obj)val).toString(buf);
|
||||||
|
else if(val!=null) buf.append(val.toString());
|
||||||
|
else buf.append("null");
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
for(int pos=0;pos<count();pos++){
|
||||||
|
if(pos>0) buf.append(",");
|
||||||
|
Slot s=getSlot(pos);
|
||||||
|
buf.append(s.getName()+":");
|
||||||
|
Object val=this.get(pos);
|
||||||
|
if(val!=null) s.toString(val,buf); else buf.append("null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.append(is_arr?"]":"}");
|
||||||
|
return buf.length()-length0;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Hdr meta(){
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean isArray(){
|
||||||
|
return meta==null || meta.checkFlags(Hdr.FLAG_ARRAY);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int count() {
|
||||||
|
return values.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Rec set(int pos, Object val) {
|
||||||
|
if(pos<0) pos=count()+pos;
|
||||||
|
values.set(pos,val);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object get(int pos) {
|
||||||
|
if(pos<0) pos=count()+pos;
|
||||||
|
return values.get(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Rec add(Object val) {
|
||||||
|
values.add(val);
|
||||||
|
if(!isArray()) meta.addSlot(new Slot("arg"+count(),Object.class));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Rec remove(int s) {
|
||||||
|
values.remove(s);
|
||||||
|
if(!isArray()) meta.removeSlot(s);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Rec set(Slot s, Object val) {
|
||||||
|
if(s==null) throw new IllegalArgumentException("invalid key provided");
|
||||||
|
if(isArray()) throw new IllegalStateException("array not mappable with:"+s.getName());
|
||||||
|
int index=s.getPosition(); // try slot position
|
||||||
|
if(index<0) index=meta.findSlot(s.getName());// fall back to search if slot not set
|
||||||
|
if(index<0){
|
||||||
|
values.add(val);
|
||||||
|
meta.addSlot(s);
|
||||||
|
}else{
|
||||||
|
values.set(index,val);
|
||||||
|
meta.setSlot(index,s);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns value by slot key.
|
||||||
|
* If the underlying rec is a vec/array this method might work if slot is positioned else it will
|
||||||
|
* return def value.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object get(Slot s, Object def) {
|
||||||
|
if(s==null) throw new IllegalArgumentException("invalid key provided");
|
||||||
|
//if(keys==null) throw new IllegalStateException("array not mappable with:"+s.getName());
|
||||||
|
int index=s.getPosition(); // try slot position
|
||||||
|
if(index<0 && !isArray()) index=meta.findSlot(s.getName());// fall back to search if slot not set
|
||||||
|
return index<0?def:values.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Rec remove(Slot s) {
|
||||||
|
int index=s.getPosition(); // try slot position
|
||||||
|
if(index<0 && !isArray()) index=meta.findSlot(s.getName());// fall back to search if slot not set
|
||||||
|
if(index>=0) remove(index);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.reliancy.rec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A record representation like in JSON.
|
||||||
|
* This is either an array or a map of fields.
|
||||||
|
* Each field definition we call a slot.
|
||||||
|
*/
|
||||||
|
public interface Rec extends Vec{
|
||||||
|
public Rec set(Slot s,Object val);
|
||||||
|
public Object get(Slot s,Object def);
|
||||||
|
public Rec remove(Slot s);
|
||||||
|
public default Slot getSlot(String name){
|
||||||
|
Hdr m=meta();
|
||||||
|
return m!=null?m.getSlot(name):null;
|
||||||
|
}
|
||||||
|
public default Slot getSlot(int pos){
|
||||||
|
Hdr m=meta();
|
||||||
|
return m!=null?m.getSlot(pos):null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.reliancy.rec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slot is a definition of a value start with the name.
|
||||||
|
* We use it to define columns/fields of records.
|
||||||
|
* It is also used as header of actual records.
|
||||||
|
*/
|
||||||
|
public class Slot extends Hdr {
|
||||||
|
|
||||||
|
public static interface Initializer{
|
||||||
|
Object getInitalValue(Slot s,Rec rec);
|
||||||
|
}
|
||||||
|
public static final Initializer DEFAULT_INITIALIZER=new Initializer(){
|
||||||
|
public Object getInitalValue(Slot s,Rec rec) {return s.getDefaultValue();}
|
||||||
|
};
|
||||||
|
int position;
|
||||||
|
Object defaultValue;
|
||||||
|
Initializer initValue;
|
||||||
|
|
||||||
|
public Slot(String name){
|
||||||
|
this(name,Object.class);
|
||||||
|
}
|
||||||
|
public Slot(String name,Class<?> type){
|
||||||
|
super(name,type);
|
||||||
|
this.position=-1;
|
||||||
|
this.initValue=DEFAULT_INITIALIZER;
|
||||||
|
}
|
||||||
|
public int getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
public void setPosition(int position) {
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
public Object getDefaultValue() {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
public void setDefaultValue(Object defaultValue) {
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
public Initializer getInitValue() {
|
||||||
|
return initValue;
|
||||||
|
}
|
||||||
|
public void setInitValue(Initializer initValue) {
|
||||||
|
this.initValue = initValue;
|
||||||
|
}
|
||||||
|
public int toString(Object val, StringBuilder buf) {
|
||||||
|
int length0=buf.length();
|
||||||
|
if(val instanceof Obj) ((Obj)val).toString(buf);
|
||||||
|
else if(val!=null) buf.append(val.toString());
|
||||||
|
else buf.append("null");
|
||||||
|
return buf.length()-length0;
|
||||||
|
}
|
||||||
|
public Object get(Rec r,Object def){
|
||||||
|
return r.get(this, def);
|
||||||
|
}
|
||||||
|
public Slot set(Rec r,Object val){
|
||||||
|
r.set(this, val);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.reliancy.rec;
|
||||||
|
|
||||||
|
/** An interface used in parser implementation.
|
||||||
|
*
|
||||||
|
* @author amer
|
||||||
|
*/
|
||||||
|
public interface TextDecoder {
|
||||||
|
void beginDocument(Rec init);
|
||||||
|
Rec endDocument();
|
||||||
|
public int parse(int offset,CharSequence in);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.reliancy.rec;
|
||||||
|
/**
|
||||||
|
* dimensioned container of values.
|
||||||
|
* Our setters return this object to make the calls chainable.
|
||||||
|
* Also positional calls accept negative values which reference from end backward.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface Vec {
|
||||||
|
public default boolean isArray(){
|
||||||
|
return meta().checkFlags(Hdr.FLAG_ARRAY);
|
||||||
|
}
|
||||||
|
public Hdr meta();
|
||||||
|
public int count();
|
||||||
|
public Rec set(int pos,Object val);
|
||||||
|
public Object get(int pos);
|
||||||
|
public Rec add(Object val);
|
||||||
|
public Rec remove(int s);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.reliancy.util;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
public interface CloseableIterator<T> extends Iterator<T>, Closeable {
|
||||||
|
}
|
||||||
@@ -0,0 +1,458 @@
|
|||||||
|
package com.reliancy.util;
|
||||||
|
/**
|
||||||
|
* Common utility methods.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.zip.DataFormatException;
|
||||||
|
import java.util.zip.Deflater;
|
||||||
|
import java.util.zip.Inflater;
|
||||||
|
|
||||||
|
public final class Handy {
|
||||||
|
public static String wrap(String verb, String left, String right) {
|
||||||
|
if(verb==null) verb="";
|
||||||
|
if(verb.startsWith(left) && verb.endsWith(right)) return verb;
|
||||||
|
return left+verb.trim()+right;
|
||||||
|
}
|
||||||
|
public static String unwrap(String verb, String left, String right) {
|
||||||
|
if(verb==null) return verb;
|
||||||
|
String ret=verb.trim();
|
||||||
|
if(verb.startsWith(left) && verb.endsWith(right)) ret=verb.substring(1,verb.length()-1);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
public static <T> T nz(T val, T def){
|
||||||
|
return val!=null?val:def;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Will try to convert incoming value to an expected class.
|
||||||
|
* @param clazz
|
||||||
|
* @param val
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
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(val instanceof String){
|
||||||
|
String value=(String) val;
|
||||||
|
if(value.isBlank() || value.equals("''") || value.equals("\"\"")) return null;
|
||||||
|
if( Boolean.class==( clazz ) || boolean.class==( clazz ) ) return Boolean.parseBoolean( value );
|
||||||
|
if( Byte.class==( clazz ) || byte.class==( clazz ) ) return Byte.parseByte( value );
|
||||||
|
if( Short.class==( clazz ) || short.class==( clazz ) ) return Short.parseShort( value );
|
||||||
|
if( Integer.class==( clazz ) || int.class==( clazz ) ) return Integer.parseInt( value );
|
||||||
|
if( Long.class==( clazz ) || long.class==( clazz )) return Long.parseLong( value );
|
||||||
|
if( Float.class==( clazz ) || float.class==( clazz ) ) return Float.parseFloat( value );
|
||||||
|
if( Double.class==( clazz ) || double.class==( clazz )) return Double.parseDouble( value );
|
||||||
|
}
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This method is a bit more complex because it locks onto two delimiters one for grouping and other
|
||||||
|
* for decimal point and chooses those from a list of [space],'`. which are used all over the world in different places.
|
||||||
|
* Returns true if the string only contains digits and numeric characters.
|
||||||
|
* This should match 1000000 also 1,000,000 and also 1,000,000.00 but it is still not possible to differentiate between 1,000 =1000 in us
|
||||||
|
* from 1000,00 whch is used in europe. So it is difficult to normalize the string so it could process any number.
|
||||||
|
* @param str string to test
|
||||||
|
* @return trie if string looks numeric or is null/empty
|
||||||
|
*/
|
||||||
|
public static final boolean isNumeric(String str){
|
||||||
|
int strLen;
|
||||||
|
if (str == null || (strLen = str.length()) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String delims=" ,.'`";
|
||||||
|
int delimNumber=0; // 0 we load, 1 we let one more, 2 we exit on second occurance
|
||||||
|
char delimUsed=0; // char used as delim
|
||||||
|
boolean delimLast=false;
|
||||||
|
int digitCount=0;
|
||||||
|
for (int i = 0; i < strLen; i++) {
|
||||||
|
char ch=str.charAt(i);
|
||||||
|
boolean accept=Character.isDigit(ch);
|
||||||
|
if(accept) digitCount++;
|
||||||
|
accept=accept || (ch=='-' && i==0);
|
||||||
|
accept=accept || (ch=='+' && i==0);
|
||||||
|
if(delims.indexOf(ch)>=0){
|
||||||
|
accept=!delimLast; // prevent delims following each other
|
||||||
|
delimLast=true;
|
||||||
|
if(delimNumber==0){
|
||||||
|
delimNumber=1;
|
||||||
|
delimUsed=ch;
|
||||||
|
}else if(delimNumber==1){
|
||||||
|
if(delimUsed!=ch) delimNumber=2;
|
||||||
|
delimUsed=ch;
|
||||||
|
}else{
|
||||||
|
// we have seen two different delim and whatever is coming here is breaking numeric format like second delim second time or some otehr
|
||||||
|
accept=false;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
delimLast=false;
|
||||||
|
}
|
||||||
|
if(!accept) return false;
|
||||||
|
}
|
||||||
|
return digitCount>0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* returns true if the string is null, empty or contains only white space.
|
||||||
|
*/
|
||||||
|
public static boolean isBlank(CharSequence str) {
|
||||||
|
int strLen;
|
||||||
|
if (str == null || (strLen = str.length()) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < strLen; i++) {
|
||||||
|
if ((Character.isWhitespace(str.charAt(i)) == false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Provides a unified notion of what constitutes an empty value.
|
||||||
|
* For any object if it is null.
|
||||||
|
* For string also if it is blank
|
||||||
|
* For arrays and lists and collection and maps also if no entries or keys exist.
|
||||||
|
* @param value anything
|
||||||
|
* @return true if any of the above matches
|
||||||
|
*/
|
||||||
|
public static boolean isEmpty(Object value){
|
||||||
|
if(value==null) return true;
|
||||||
|
if(value instanceof CharSequence){
|
||||||
|
return isBlank((CharSequence)value);
|
||||||
|
}
|
||||||
|
Class<?> cls=value.getClass();
|
||||||
|
if(cls.isArray()) {
|
||||||
|
if(value instanceof Object[]){
|
||||||
|
Object[] arr=(Object[]) value;
|
||||||
|
if(arr.length==0) return true;
|
||||||
|
for(int i=0;i<arr.length;i++) if(isEmpty(arr[i])==false) return false;
|
||||||
|
return true;
|
||||||
|
}if(value instanceof byte[]){
|
||||||
|
return ((byte[])value).length==0;
|
||||||
|
}else if(value instanceof short[]){
|
||||||
|
return ((short[])value).length==0;
|
||||||
|
}else if(value instanceof int[]){
|
||||||
|
return ((int[])value).length==0;
|
||||||
|
}else if(value instanceof long[]){
|
||||||
|
return ((long[])value).length==0;
|
||||||
|
}else if(value instanceof float[]){
|
||||||
|
return ((float[])value).length==0;
|
||||||
|
}else if(value instanceof double[]){
|
||||||
|
return ((double[])value).length==0;
|
||||||
|
}else{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(value instanceof Collection){
|
||||||
|
Collection<?> c=(Collection<?>)value;
|
||||||
|
if(c.isEmpty()) return true;
|
||||||
|
for(Object o:c){
|
||||||
|
if(isEmpty(o)==false) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/** Attempts to take a compact string and beautify it.
|
||||||
|
* Will uppercase the first letter. Will also expand CamelCase.
|
||||||
|
* Also will replace _ with empty space.
|
||||||
|
* @param str
|
||||||
|
* @return nicely formatted string ready for display
|
||||||
|
*/
|
||||||
|
public static final String prettyPrint(String str){
|
||||||
|
if(str==null) return "";
|
||||||
|
boolean fix=false;
|
||||||
|
char prevCh=0;
|
||||||
|
if(str.startsWith("org.") || str.startsWith("net.") || str.startsWith("com.") || str.startsWith("java.")){
|
||||||
|
str=str.substring(1+str.lastIndexOf('.')); // we strip class name paths
|
||||||
|
}
|
||||||
|
for(int i=0;i<str.length();i++){
|
||||||
|
char currCh=str.charAt(i);
|
||||||
|
if(i==0 && Character.isLowerCase(currCh)) fix=true;
|
||||||
|
if(Character.isLowerCase(prevCh) && Character.isUpperCase(currCh)) fix=true;
|
||||||
|
if(Character.isUpperCase(prevCh) && Character.isUpperCase(currCh) && i<(str.length()-1) && Character.isLowerCase(str.charAt(i+1))) fix=true;
|
||||||
|
if(!Character.isLetter(currCh)) fix=true;
|
||||||
|
prevCh=currCh;
|
||||||
|
}
|
||||||
|
if(!fix) return str;
|
||||||
|
StringBuilder bufs=new StringBuilder();
|
||||||
|
boolean toUC=false;
|
||||||
|
for(int i=0;i<str.length();i++){
|
||||||
|
char currCh=str.charAt(i);
|
||||||
|
if(currCh=='_') currCh=' ';
|
||||||
|
prevCh=bufs.length()>0?bufs.charAt(bufs.length()-1):currCh;
|
||||||
|
if(Character.isWhitespace(currCh)){
|
||||||
|
if(!Character.isWhitespace(prevCh)){
|
||||||
|
bufs.append(' ');
|
||||||
|
}
|
||||||
|
continue; // ignore repeated whitespace otherwise emit space
|
||||||
|
}
|
||||||
|
if(bufs.length()==0){
|
||||||
|
toUC=true;
|
||||||
|
}else if((!Character.isUpperCase(prevCh) && ("-+/%*".indexOf(prevCh)==-1 || Character.isLetter(prevCh))) && Character.isUpperCase(currCh)){
|
||||||
|
// non uc (a not one of operands) behind, uc ahead
|
||||||
|
bufs.append(" ");
|
||||||
|
}else if(Character.isLetter(prevCh) && Character.isDigit(currCh)){
|
||||||
|
// letter behind, digit ahead
|
||||||
|
bufs.append(" ");
|
||||||
|
}else if(Character.isUpperCase(prevCh) && Character.isUpperCase(currCh) && i<(str.length()-1) && Character.isLowerCase(str.charAt(i+1))){
|
||||||
|
// behind me uppercase infrom uppercase then lowercase
|
||||||
|
bufs.append(" ");
|
||||||
|
}
|
||||||
|
bufs.append(toUC?Character.toUpperCase(currCh):currCh);
|
||||||
|
toUC=false;
|
||||||
|
}
|
||||||
|
while(bufs.length()>0 && Character.isWhitespace(bufs.charAt(bufs.length()-1))){
|
||||||
|
// trims whitespace from end
|
||||||
|
bufs.setLength(bufs.length()-1);
|
||||||
|
}
|
||||||
|
return bufs.toString();
|
||||||
|
}
|
||||||
|
/** Attempts to take a user string and compact it to camel case.
|
||||||
|
* @param str nicely formatted string
|
||||||
|
* @return nicely compact string
|
||||||
|
*/
|
||||||
|
public static String toCamelCase(String value) {
|
||||||
|
if(value==null || value.trim().isEmpty()) return "";
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
//final char delimChar = ' ';
|
||||||
|
boolean flip = false;
|
||||||
|
for (int charInd = 0; charInd < value.length(); charInd++) {
|
||||||
|
char ch = value.charAt(charInd);
|
||||||
|
if (Character.isWhitespace(ch)) {
|
||||||
|
flip = true;
|
||||||
|
}else if(flip){
|
||||||
|
flip = false;
|
||||||
|
if(ch==Character.toLowerCase(ch)) sb.append("_");
|
||||||
|
sb.append(ch);
|
||||||
|
}else{
|
||||||
|
sb.append(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
public static byte[] deflate(byte[] content) throws IOException{
|
||||||
|
Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION,true);
|
||||||
|
deflater.setInput(content);
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(content.length);
|
||||||
|
deflater.finish();
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
while (!deflater.finished()) {
|
||||||
|
int count = deflater.deflate(buffer); // returns the generated code... index
|
||||||
|
outputStream.write(buffer, 0, count);
|
||||||
|
}
|
||||||
|
outputStream.close();
|
||||||
|
byte[] output = outputStream.toByteArray();
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] inflate(byte[] contentBytes) throws IOException, DataFormatException{
|
||||||
|
Inflater inflater = new Inflater(true);
|
||||||
|
inflater.setInput(contentBytes);
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(contentBytes.length);
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
while (!inflater.finished()) {
|
||||||
|
int count = inflater.inflate(buffer);
|
||||||
|
outputStream.write(buffer, 0, count);
|
||||||
|
}
|
||||||
|
outputStream.close();
|
||||||
|
byte[] output = outputStream.toByteArray();
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void shuffle(Object[] e){
|
||||||
|
Random rn = new Random();
|
||||||
|
for(int i=0;i<e.length;i++){
|
||||||
|
int other=rn.nextInt(e.length);
|
||||||
|
Object tmp=e[i];
|
||||||
|
e[i]=e[other];
|
||||||
|
e[other]=tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static String encodeBase64(byte[] data){
|
||||||
|
return Base64.getEncoder().encodeToString(data);
|
||||||
|
}
|
||||||
|
public static byte[] decodeBase64(String data){
|
||||||
|
return Base64.getDecoder().decode(data);
|
||||||
|
}
|
||||||
|
/** Simple XOR encryption of a map of key-value pairs.
|
||||||
|
* We randomize the order of key value pairs to make the string more unpredictable.
|
||||||
|
* Returned string is base64 and web safe
|
||||||
|
* @param key
|
||||||
|
* @param m
|
||||||
|
* @return a string of encoded map key-value pairs which were then encrypted
|
||||||
|
*/
|
||||||
|
public static final String encrypt(String key,Map<String,String> m){
|
||||||
|
String ret=null;
|
||||||
|
Object[] es=m.keySet().toArray();
|
||||||
|
shuffle(es); // we shuffle entries to confuse the string a bit
|
||||||
|
StringBuilder buf=new StringBuilder();
|
||||||
|
for(int i=0;i<es.length;i++){
|
||||||
|
Object e=es[i];
|
||||||
|
if(i>0) buf.append("\n");
|
||||||
|
buf.append(e).append(":").append(m.get(e));
|
||||||
|
}
|
||||||
|
ret=encryptString(key,buf.toString());
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This method will encrypt a string and return BASE 64 string that is web safe.
|
||||||
|
* TO make the string web safe we replace + with - and / with _
|
||||||
|
* Must revert this change on the reverse.
|
||||||
|
* @param key
|
||||||
|
* @param ret
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static final String encryptString(String key,String ret){
|
||||||
|
try{
|
||||||
|
byte[] bkey=key.getBytes("UTF-8");
|
||||||
|
byte[] bstr=ret.getBytes("UTF-8");
|
||||||
|
for(int i=0;i<bstr.length;i++){
|
||||||
|
bstr[i]=(byte)(bstr[i] ^ bkey[i%bkey.length]);
|
||||||
|
}
|
||||||
|
// now need to encode this
|
||||||
|
ret=encodeBase64(bstr);
|
||||||
|
ret=ret.replace('+','-');
|
||||||
|
ret=ret.replace('/','_');
|
||||||
|
ret=ret.replace('=','.');
|
||||||
|
ret=ret.replace("\n","");
|
||||||
|
}catch(Exception e){
|
||||||
|
ret="";
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
/**Reverses the effects of encrypt.
|
||||||
|
* Also changes
|
||||||
|
* @param key
|
||||||
|
* @param m
|
||||||
|
* @return values decrypted and parsed into key-value pair along newline.
|
||||||
|
*/
|
||||||
|
public static final Map<String,String> decrypt(String key,String m){
|
||||||
|
m=decryptString(key,m);
|
||||||
|
Map<String,String> ret=new HashMap<>();
|
||||||
|
//System.out.println("Output:"+m);
|
||||||
|
Tokenizer tokz=new Tokenizer(m);
|
||||||
|
tokz.setDelimChars("\n");
|
||||||
|
tokz.setWhiteChars(null);
|
||||||
|
for(String t=tokz.nextToken();t!=null;t=tokz.nextToken()){
|
||||||
|
if("\n".equals(t)) continue;
|
||||||
|
String[] kv=t.split(":",2);
|
||||||
|
ret.put(kv[0],kv.length>1?kv[1]:null);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
public static final String decryptString(String key,String m){
|
||||||
|
try{
|
||||||
|
//m=URLDecoder.decode(m, "UTF-8");
|
||||||
|
m=m.replace('-','+');
|
||||||
|
m=m.replace('_','/');
|
||||||
|
m=m.replace('.','=');
|
||||||
|
byte[] bkey=key.getBytes("UTF-8");
|
||||||
|
byte[] bstr=decodeBase64(m);
|
||||||
|
for(int i=0;i<bstr.length;i++){
|
||||||
|
bstr[i]=(byte)(bstr[i] ^ bkey[i%bkey.length]);
|
||||||
|
}
|
||||||
|
m=new String(bstr,"UTF-8");
|
||||||
|
}catch(Exception e){
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Generates a hash string with the algorithm name prefixed.
|
||||||
|
* @param message
|
||||||
|
* @param algorithm
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String hashString(String message, String algorithm) throws NoSuchAlgorithmException, UnsupportedEncodingException{
|
||||||
|
if(message==null) return message;
|
||||||
|
MessageDigest digest = MessageDigest.getInstance(algorithm);
|
||||||
|
byte[] hashedBytes = digest.digest(message.getBytes("UTF-8"));
|
||||||
|
return algorithm.toLowerCase()+":"+encodeBase64(hashedBytes);
|
||||||
|
}
|
||||||
|
public static String hashSHA256(String message){
|
||||||
|
try{
|
||||||
|
return hashString(message,"SHA-256");
|
||||||
|
}catch(Exception ex){
|
||||||
|
return "sha-256:"+Integer.toHexString(message.hashCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static String hashMD5(String input){
|
||||||
|
try {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
|
byte[] messageDigest = md.digest(input.getBytes());
|
||||||
|
BigInteger no = new BigInteger(1, messageDigest);
|
||||||
|
String hashtext = no.toString(16);
|
||||||
|
while (hashtext.length() < 32) {
|
||||||
|
hashtext = "0" + hashtext;
|
||||||
|
}
|
||||||
|
return hashtext;
|
||||||
|
}catch (NoSuchAlgorithmException e) { // For specifying wrong message digest algorithms
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static String toHexString(byte[] hash){
|
||||||
|
char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
|
||||||
|
StringBuilder sb = new StringBuilder(hash.length * 2);
|
||||||
|
for (byte b : hash) {
|
||||||
|
sb.append(HEX_CHARS[(b & 0xF0) >> 4]);
|
||||||
|
sb.append(HEX_CHARS[b & 0x0F]);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Finds first occurrence of sub inside body with and without case.
|
||||||
|
* We implement this search via a FSM and ignore the case.
|
||||||
|
* @param body
|
||||||
|
* @param sub
|
||||||
|
* @param offset
|
||||||
|
* @return offset of first occurance
|
||||||
|
*/
|
||||||
|
public static final int indexOf(CharSequence body,CharSequence sub,int offset){
|
||||||
|
if(body==null) return -1;
|
||||||
|
int state=0;
|
||||||
|
int blen=body.length();
|
||||||
|
int slen=sub.length();
|
||||||
|
boolean ignorecase=true;
|
||||||
|
for(int index=offset;index<blen;index++){
|
||||||
|
char bC=body.charAt(index);
|
||||||
|
char sC=sub.charAt(state);
|
||||||
|
if(ignorecase){
|
||||||
|
bC=Character.toLowerCase(bC);
|
||||||
|
sC=Character.toLowerCase(sC);
|
||||||
|
}
|
||||||
|
if(bC==sC) state+=1; else state=0;
|
||||||
|
if(state>=slen) return index-slen+1; // we found a match
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Will trim the string from left and right and remove any of the symbols.
|
||||||
|
*/
|
||||||
|
public static String trim(String trim,String sym) {
|
||||||
|
if(trim==null || trim.length()==0) return trim;
|
||||||
|
int start=0;
|
||||||
|
int end=trim.length();
|
||||||
|
while(start<trim.length() && sym.indexOf(trim.charAt(start))!=-1) start++;
|
||||||
|
while(0<end && sym.indexOf(trim.charAt(end-1))!=-1) end--;
|
||||||
|
if(start==0 && end==trim.length()) return trim;
|
||||||
|
return start<end?trim.substring(start, end):"";
|
||||||
|
}
|
||||||
|
/** will copy contents of a list into a fixed length array */
|
||||||
|
public static String[] asArray(List<String> all) {
|
||||||
|
if(all==null) return null;
|
||||||
|
String[] ret=new String[all.size()];
|
||||||
|
all.toArray(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,418 @@
|
|||||||
|
/*
|
||||||
|
* To change this template, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package com.reliancy.util;
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
|
||||||
|
|
||||||
|
/** Path to a resource almost identical to a URL.
|
||||||
|
* It might but should not hold any handles. It holds the address and
|
||||||
|
* possibly takes care of looking up addresses.
|
||||||
|
* The richest syntax is:
|
||||||
|
* PROTOCOL://USER:PWD@MACHINE:PORT/DATABASE?key=val&...
|
||||||
|
* Properties are held in their own string and need to be decoded.
|
||||||
|
*
|
||||||
|
* We use forward slash for path delimitation of database portion. For the rest we preserve other slashes to allow windows domain\\user or server\\instance.
|
||||||
|
* Special chars are [/@?,:;]
|
||||||
|
* if any are found at the end of protocol we skip ://
|
||||||
|
* if any are found at the beginning of properties we skip ?
|
||||||
|
* if any are found at the beginning of database we skip /
|
||||||
|
*
|
||||||
|
* In windows we have volume or drive letters which are postfixed by colon : then slashes. We treat the volume as part of database.
|
||||||
|
* We store the database without first slash to allow specification of relative paths.
|
||||||
|
* To render it as absolute set protocol or host to empty string instead of null. Then a slash will be prefixed and you will get absolute path.
|
||||||
|
* @author amer
|
||||||
|
*/
|
||||||
|
public class Path {
|
||||||
|
static final String SYMBOLS="/@?,:;";
|
||||||
|
String connectstring;
|
||||||
|
String protocol; ///< protocol guides the interpretation of the other elements in conn, string
|
||||||
|
String userid; ///< authentication
|
||||||
|
String password; ///< authorization
|
||||||
|
String host; ///< machine or computer
|
||||||
|
String port; ///< access to computer
|
||||||
|
String database; ///< name of database or filename
|
||||||
|
String properties; ///< properties are what follows ? in a url
|
||||||
|
|
||||||
|
public Path(String connect) {
|
||||||
|
setConnectString(connect);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path(Path in) {
|
||||||
|
connectstring=in.connectstring;
|
||||||
|
protocol=in.protocol;
|
||||||
|
userid=in.userid;
|
||||||
|
password=in.password;
|
||||||
|
host=in.host;
|
||||||
|
port=in.port;
|
||||||
|
database=in.database;
|
||||||
|
properties=in.properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getConnectString();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Converts the path to a file.
|
||||||
|
* If absolute is true
|
||||||
|
* @param absolute if true forms absolute path else will return relative path
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public File toFile(boolean absolute){
|
||||||
|
String proto=getProtocol();
|
||||||
|
String host=getHost();
|
||||||
|
try{
|
||||||
|
setProtocol(absolute?"":null);
|
||||||
|
setHost(absolute?"":null);
|
||||||
|
String path=toString();
|
||||||
|
return new File(path);
|
||||||
|
}finally{
|
||||||
|
setProtocol(proto);
|
||||||
|
setHost(host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public URL toURL() throws MalformedURLException{
|
||||||
|
String path=toString();
|
||||||
|
if(Handy.isBlank(getHost()) && path.contains("://") && !path.contains(":///")) path=path.replace("://",":///");
|
||||||
|
return new URL(path);
|
||||||
|
}
|
||||||
|
public void clear(){
|
||||||
|
connectstring=null;
|
||||||
|
protocol=null;
|
||||||
|
userid=null;
|
||||||
|
password=null;
|
||||||
|
host=null;
|
||||||
|
port=null;
|
||||||
|
database=null;
|
||||||
|
properties=null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getConnectString() {
|
||||||
|
if(connectstring!=null) return connectstring;
|
||||||
|
// assemble the connect string
|
||||||
|
StringBuilder buf=new StringBuilder();
|
||||||
|
//boolean absolute=false;
|
||||||
|
if(!Handy.isBlank(protocol)){
|
||||||
|
buf.append(protocol);
|
||||||
|
if(SYMBOLS.indexOf(protocol.charAt(protocol.length()-1))<0) buf.append("://");
|
||||||
|
}
|
||||||
|
if(!Handy.isBlank(host)){
|
||||||
|
if(userid!=null && password!=null){
|
||||||
|
buf.append(userid).append(":").append(password).append("@");
|
||||||
|
}
|
||||||
|
buf.append(host);
|
||||||
|
if(port!=null) buf.append(":").append(port);
|
||||||
|
}
|
||||||
|
if(!Handy.isBlank(database)){
|
||||||
|
if(buf.length()>0 && SYMBOLS.indexOf(database.charAt(0))<0){
|
||||||
|
// we got something in front so we need to use slash
|
||||||
|
buf.append("/");
|
||||||
|
}else if(protocol!=null || host!=null){
|
||||||
|
boolean winvol=database.length()>2 && database.charAt(1)==':' && (database.charAt(2)=='/' || database.charAt(2)=='\\');
|
||||||
|
// we got nothing in front but if host or protocol empty but not null we treat as absolute
|
||||||
|
if(!winvol) buf.append("/");
|
||||||
|
}
|
||||||
|
buf.append(database);
|
||||||
|
}
|
||||||
|
if(properties!=null){
|
||||||
|
if(SYMBOLS.indexOf(properties.charAt(0))<0) buf.append("?");
|
||||||
|
buf.append(properties);
|
||||||
|
}
|
||||||
|
connectstring=buf.toString();
|
||||||
|
return connectstring;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConnectString(String connect) {
|
||||||
|
clear();
|
||||||
|
if (connect == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.connectstring=connect;
|
||||||
|
// first get protocol - everything up to : which is not followed by a symbol (includes :// but also c:/
|
||||||
|
int oldst=0;
|
||||||
|
int st=0;
|
||||||
|
for(int i=0;i<(connectstring.length()-1);i++){
|
||||||
|
char curr=connectstring.charAt(i);
|
||||||
|
if(curr==':'){
|
||||||
|
oldst=st;
|
||||||
|
st=i;
|
||||||
|
}else if(SYMBOLS.indexOf(curr)!=-1){
|
||||||
|
if(curr=='@') st=oldst; // this will back out one : if protocl search ended with @ indicating a server
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(st==1){ st=0;} // this will supress single letter protocols i.e. c:/ ued in windows as part of database/file
|
||||||
|
if(2==(st-oldst)) st=oldst;
|
||||||
|
if(st>0){
|
||||||
|
this.protocol=connectstring.substring(0,st);
|
||||||
|
while(SYMBOLS.indexOf(connectstring.charAt(st))!=-1) st++; // advance over symbols
|
||||||
|
}
|
||||||
|
// next assume the rest is a file/database
|
||||||
|
database = connectstring.substring(st);
|
||||||
|
// now check for user id and password
|
||||||
|
st = database.indexOf('@');
|
||||||
|
boolean checkhost=st>=0;
|
||||||
|
if(!Handy.isBlank(protocol)){
|
||||||
|
checkhost=!protocol.contains(":file") && !protocol.contains(":mem") && !protocol.equals("file") && !protocol.equals("mem");;
|
||||||
|
}
|
||||||
|
if (st != -1) {
|
||||||
|
userid = database.substring(0, st);
|
||||||
|
if(userid.contains("%4")) try{userid=URLDecoder.decode(userid,"UTF-8");}catch(Exception e){}
|
||||||
|
database = database.substring(st + 1);
|
||||||
|
// now try to split user id into password if possible
|
||||||
|
st = userid.indexOf(':');
|
||||||
|
if (st != -1) {
|
||||||
|
password = userid.substring(st + 1);
|
||||||
|
userid = userid.substring(0, st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ok next try to split up machine if possible (only for absolute urls)
|
||||||
|
st = database.indexOf(':');
|
||||||
|
if(st<0) st=database.indexOf('/');
|
||||||
|
if (st != -1 && checkhost) {
|
||||||
|
boolean portfollows=database.charAt(st)==':';
|
||||||
|
host = database.substring(0, st);
|
||||||
|
// now try to recover port
|
||||||
|
if(portfollows){
|
||||||
|
int st2 = database.indexOf(':',st+1);
|
||||||
|
if(st2<0) st2 = database.indexOf('/',st+1);
|
||||||
|
if (st2 != -1 && st2>(st+1)) { // we have a port
|
||||||
|
port = database.substring(st + 1,st2);
|
||||||
|
st=st2;
|
||||||
|
}else{
|
||||||
|
// no port we have : then / - which is used in windows to indicate volume and we treat as part of database
|
||||||
|
st=-1;
|
||||||
|
host="";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
database = database.substring(st + 1);
|
||||||
|
}
|
||||||
|
database=fixSlashes(database);
|
||||||
|
// finally split the properties from database
|
||||||
|
st = database.indexOf('?');
|
||||||
|
if(st==-1) st=database.indexOf(';');
|
||||||
|
if (st != -1) { // we have properties
|
||||||
|
properties = database.substring(st);
|
||||||
|
database = database.substring(0, st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Absolute ResourcePath will have a protocol
|
||||||
|
*/
|
||||||
|
public boolean isAbsolute() {
|
||||||
|
return (protocol != null || host!=null);
|
||||||
|
}
|
||||||
|
/// will clear host and protocol using empty string thereby making database absolute path
|
||||||
|
public Path setAbsolute(){
|
||||||
|
setHost("");
|
||||||
|
setProtocol("");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDatabase() {
|
||||||
|
return database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path setDatabase(String database) {
|
||||||
|
this.database = database;
|
||||||
|
connectstring=null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHost() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path setHost(String host) {
|
||||||
|
this.host = host;
|
||||||
|
connectstring=null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
connectstring=null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPort() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path setPort(String port) {
|
||||||
|
this.port = port;
|
||||||
|
connectstring=null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProtocol() {
|
||||||
|
return protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path setProtocol(String protocol) {
|
||||||
|
this.protocol = protocol;
|
||||||
|
connectstring=null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserid() {
|
||||||
|
return userid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path setUserid(String userid) {
|
||||||
|
this.userid = userid;
|
||||||
|
connectstring=null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public String getProperties() {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path setProperties(String userid) {
|
||||||
|
this.properties = userid;
|
||||||
|
connectstring=null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getBase() {
|
||||||
|
return Path.getBase(database);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExtension() {
|
||||||
|
return Path.getExtension(database);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPathItem() {
|
||||||
|
return Path.getPathItem(database);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ensures that we use forward slashes and that single dot is not present mid or and the end.
|
||||||
|
*
|
||||||
|
* @param path a unix or windows or uri path
|
||||||
|
* @return a path with forward slashes
|
||||||
|
*/
|
||||||
|
public static String fixSlashes(String path) {
|
||||||
|
if(path==null || path.length()==0) return path;
|
||||||
|
path=path.replace("\\", "/");
|
||||||
|
path=path.replace("/./","/");
|
||||||
|
while(true){
|
||||||
|
if(path.endsWith("/")) path=path.substring(0,path.length()-1);
|
||||||
|
else if(path.endsWith("/.")) path=path.substring(0,path.length()-2);
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** returns database path given path and file.
|
||||||
|
* We assume the path uses forward backslash for delimitation.
|
||||||
|
*/
|
||||||
|
public static String getBase(String path) {
|
||||||
|
int st1 = path.lastIndexOf('/');
|
||||||
|
int st2 = path.lastIndexOf('\\');
|
||||||
|
int st=st2>st1?st2:st1;
|
||||||
|
if (st == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return path.substring(0, st);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getExtension(String path) {
|
||||||
|
int st=Math.max(path.lastIndexOf('/'),path.lastIndexOf('\\'));
|
||||||
|
int st2 = path.lastIndexOf('.');
|
||||||
|
if (st2 == -1 || (st>0 && st2<st)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return path.substring(st2 + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPathItem(String path) {
|
||||||
|
path=path.replace('\\','/');
|
||||||
|
int st11 = path.lastIndexOf('/');
|
||||||
|
int st1=1+st11;
|
||||||
|
int st2 = path.lastIndexOf('.');
|
||||||
|
if (st2 <0) {
|
||||||
|
st2 = path.length();
|
||||||
|
}
|
||||||
|
return path.substring(st1, st2);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Assuming that url starts with base will return string beyond base in url.
|
||||||
|
* @param base
|
||||||
|
* @param url
|
||||||
|
*/
|
||||||
|
public static String getRemainder(String base,String url){
|
||||||
|
if(base.length()>=url.length()) return null;
|
||||||
|
return url.substring(base.length());
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* unites two paths.
|
||||||
|
* @param base
|
||||||
|
* @param url
|
||||||
|
*/
|
||||||
|
public static String getUnion(String base,String url){
|
||||||
|
if(base==null || base.isEmpty()){
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
if(url==null || url.isEmpty()){
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
//if(url.startsWith(base)) return url;
|
||||||
|
if(Handy.indexOf(url,base,0)==0) return url;
|
||||||
|
StringBuilder ret=new StringBuilder();
|
||||||
|
ret.append(base);
|
||||||
|
if(!base.endsWith("/") && !url.startsWith("/")) ret.append("/");
|
||||||
|
if(base.endsWith("/") && url.startsWith("/")) ret.setLength(ret.length()-1);
|
||||||
|
ret.append(url);
|
||||||
|
return ret.toString();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* method will split paths used in linux and windows.
|
||||||
|
* in particular for windows it checks if a single letter precedes a colon in which case it considers it a volume
|
||||||
|
* and does not split there.
|
||||||
|
* @param paths paths joined with colon or semi-colon
|
||||||
|
* @return array of paths
|
||||||
|
*/
|
||||||
|
public static String[] splitPaths(String _paths){
|
||||||
|
String[] paths=_paths.replaceAll("(;|:|^)([a-zA-Z]):","$1$2##").split("[:;]");
|
||||||
|
for (int i = 0; i < paths.length; i++) {
|
||||||
|
String path=paths[i];
|
||||||
|
path = path.replace("##",":");
|
||||||
|
path=path.replace("/./","/");
|
||||||
|
path=path.replace("//","/");
|
||||||
|
path=path.replace("\\.\\","\\");
|
||||||
|
path=path.replace("\\\\","\\");
|
||||||
|
paths[i]=path;
|
||||||
|
}
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of key,value pairs in the order they occur in the string str.
|
||||||
|
* @param str
|
||||||
|
*/
|
||||||
|
public static String[] splitProperties(String str) {
|
||||||
|
if(str.startsWith("?")) str=str.substring(1);
|
||||||
|
if(str.startsWith(";")) str=str.substring(1);
|
||||||
|
return str.split("&");
|
||||||
|
}
|
||||||
|
public static String[] splitKeyValue(String str) {
|
||||||
|
String[] t=str.split("=");
|
||||||
|
if(t==null || t.length==0) return null;
|
||||||
|
t[0]=Handy.trim(t[0],"'\"");
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
public static String[] split(String str) {
|
||||||
|
return str.split("/");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package com.reliancy.util;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class Resources {
|
||||||
|
public static interface PathRewrite{
|
||||||
|
public String rewritePath(String path,Object context);
|
||||||
|
}
|
||||||
|
public static URL findFirst(PathRewrite remap,String path,Object ... sp){
|
||||||
|
for(Object base:sp){
|
||||||
|
if(remap!=null) path=remap.rewritePath(path,base);
|
||||||
|
if(base instanceof Class){
|
||||||
|
URL ret=((Class<?>)base).getResource(path);
|
||||||
|
return ret;
|
||||||
|
}else if(base instanceof String){
|
||||||
|
File ff=new File(base.toString(),path);
|
||||||
|
if(ff.exists()){
|
||||||
|
try {
|
||||||
|
return ff.toURI().toURL();
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else if(base instanceof File){
|
||||||
|
File ff=new File((File)base,path);
|
||||||
|
if(ff.exists()){
|
||||||
|
try {
|
||||||
|
return ff.toURI().toURL();
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else if(base instanceof URL){
|
||||||
|
try {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if(proto.equals("file")){
|
||||||
|
File f=new File(ret.getPath());
|
||||||
|
if(f.exists()) return ret;
|
||||||
|
}
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
continue;
|
||||||
|
} catch (IOException e2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public static String toString(URL url) throws IOException{
|
||||||
|
return toString(url,StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
public static String toString(URL url,Charset chs) throws IOException{
|
||||||
|
try(InputStream is=url.openStream()){
|
||||||
|
return readChars(is,chs).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static byte[] toBytes(URL url) throws IOException{
|
||||||
|
try(InputStream is=url.openStream()){
|
||||||
|
return readBytes(is);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static long copy(InputStream input, OutputStream output, byte[] buffer) throws IOException {
|
||||||
|
long count = 0;
|
||||||
|
int n = 0;
|
||||||
|
while (-1 != (n = input.read(buffer))) {
|
||||||
|
output.write(buffer, 0, n);
|
||||||
|
count += n;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
public static long copy(Reader input, Writer output, char[] buffer) throws IOException {
|
||||||
|
long count = 0;
|
||||||
|
int n = 0;
|
||||||
|
while (-1 != (n = input.read(buffer))) {
|
||||||
|
output.write(buffer, 0, n);
|
||||||
|
count += n;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Reads a stream in one pass and returns bytes.
|
||||||
|
* Uses internally Handy.copy and a 4K buffer.
|
||||||
|
*/
|
||||||
|
public static final byte[] readBytes(InputStream str) throws IOException{
|
||||||
|
ByteArrayOutputStream bout=new ByteArrayOutputStream();
|
||||||
|
Resources.copy(str, bout, new byte[4096]);
|
||||||
|
return bout.toByteArray();
|
||||||
|
}
|
||||||
|
public static final CharSequence readChars(InputStream str) throws IOException{
|
||||||
|
return readChars(str,StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
public static final CharSequence readChars(InputStream str,Charset chset) throws IOException{
|
||||||
|
BufferedReader rdr=new BufferedReader(new InputStreamReader(str,chset));
|
||||||
|
StringBuilder ret=new StringBuilder();
|
||||||
|
for(String line=rdr.readLine();line!=null;line=rdr.readLine()){
|
||||||
|
ret.append(line).append("\n");
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
public static CharSequence readChars(Class<?> cls,String name){
|
||||||
|
InputStream io=cls.getResourceAsStream(name);
|
||||||
|
try{
|
||||||
|
return readChars(io);
|
||||||
|
}catch(Exception e){
|
||||||
|
return null;
|
||||||
|
}finally{
|
||||||
|
if(io!=null) try{io.close();}catch(Exception e){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static void writeChars(CharSequence seq,OutputStream out,Charset chset) throws IOException{
|
||||||
|
OutputStreamWriter dout=new OutputStreamWriter(out,chset);
|
||||||
|
dout.append(seq);
|
||||||
|
dout.flush();
|
||||||
|
}
|
||||||
|
public static void writeChars(CharSequence seq,OutputStream out) throws IOException{
|
||||||
|
writeChars(seq,out,StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
public static void writeBytes(int offset,int len,byte[] seq,OutputStream out) throws IOException{
|
||||||
|
out.write(seq,offset, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package com.reliancy.util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import com.hubspot.jinjava.Jinjava;
|
||||||
|
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
|
||||||
|
import com.hubspot.jinjava.loader.ResourceLocator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We will manage template rendering thru this class.
|
||||||
|
*/
|
||||||
|
public class Template {
|
||||||
|
static Jinjava jinjava;
|
||||||
|
static{
|
||||||
|
jinjava = new Jinjava();
|
||||||
|
jinjava.setResourceLocator(new JinjaLoader());
|
||||||
|
}
|
||||||
|
public static class JinjaLoader implements ResourceLocator{
|
||||||
|
@Override
|
||||||
|
public String getString(String fullName, Charset encoding, JinjavaInterpreter interpreter) throws IOException {
|
||||||
|
URL loc=Resources.findFirst(null,fullName,Template.search_path);
|
||||||
|
if(loc==null){
|
||||||
|
Logger.getLogger(Template.class.getSimpleName()).warning("Missing template"+fullName);
|
||||||
|
return "";
|
||||||
|
}else{
|
||||||
|
return Resources.toString(loc,encoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static Object[] search_path;
|
||||||
|
static HashMap<String,Template> cache=new HashMap<>();
|
||||||
|
/** renders a template to string, possibly locates it first.
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* @param context
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static CharSequence render(String path,Map<String,?> context) throws IOException{
|
||||||
|
Template t=find(path,search_path);
|
||||||
|
if(t==null){
|
||||||
|
return null;
|
||||||
|
}else{
|
||||||
|
return t.render(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** returns a template based on a URL located over a search path.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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));
|
||||||
|
System.out.println("TLOCL:"+loc);
|
||||||
|
if(loc==null) return null;
|
||||||
|
ret=new Template(loc);
|
||||||
|
cache.put(path,ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
public static Object[] search_path(Object...sp){
|
||||||
|
if(sp!=null && sp.length>0) search_path=sp;
|
||||||
|
return search_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
final URL location;
|
||||||
|
String source;
|
||||||
|
public Template(URL location){
|
||||||
|
this.location=location;
|
||||||
|
}
|
||||||
|
public Template(String src){
|
||||||
|
this.location=null;
|
||||||
|
this.source=src;
|
||||||
|
}
|
||||||
|
public URL getLocation(){
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
public String getSource(){
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
public Template load() throws IOException{
|
||||||
|
if(source==null) this.source=Resources.toString(location);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public CharSequence render(Map<String,?> context) throws IOException{
|
||||||
|
if(source==null) load();
|
||||||
|
String ret = jinjava.render(source, context);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
package com.reliancy.util;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/** A utility to help us tokenize text along delimChars.
|
||||||
|
* This class is a little better than the java version because it allows for escaped delimChars.
|
||||||
|
* Delimiters are escaped with a slash, also single and double quotes supress delimiting when encountered.
|
||||||
|
* @author amer
|
||||||
|
*/
|
||||||
|
public class Tokenizer implements Iterable<String>,Iterator<String>{
|
||||||
|
public static final String WHITECHARS=" \t\r\f\n";
|
||||||
|
public static final String DELIMCHARS=" ,:;=<>{}[]()";
|
||||||
|
int offset;
|
||||||
|
CharSequence input;
|
||||||
|
String delimChars=DELIMCHARS;
|
||||||
|
String escapeChars="'\"";
|
||||||
|
String whiteChars=WHITECHARS;
|
||||||
|
public Tokenizer(CharSequence input){
|
||||||
|
this.input=input;
|
||||||
|
}
|
||||||
|
public Tokenizer(CharSequence input,int offset){
|
||||||
|
this.input=input;
|
||||||
|
this.offset=offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence getInput() {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOffset() {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tokenizer setOffset(int offset) {
|
||||||
|
this.offset = offset;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tokenizer setInput(CharSequence input) {
|
||||||
|
this.input = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public boolean hasMoreTokens(){
|
||||||
|
if(offset>=input.length()) return false;
|
||||||
|
for(int i=offset;i<input.length();i++){
|
||||||
|
char ch=input.charAt(i);
|
||||||
|
if(Tokenizer.isElementOf(ch,whiteChars)==-1) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public String nextToken(){
|
||||||
|
final StringBuilder out=new StringBuilder();
|
||||||
|
if(nextToken(out)){
|
||||||
|
return out.toString();
|
||||||
|
}else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public boolean nextToken(StringBuilder out){
|
||||||
|
String[] sets={delimChars,escapeChars,whiteChars};
|
||||||
|
int noffset=nextToken(offset,input,out,sets);
|
||||||
|
if(noffset==offset){
|
||||||
|
return false;
|
||||||
|
}else{
|
||||||
|
offset=noffset;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDelimChars() {
|
||||||
|
return delimChars;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tokenizer setDelimChars(String delimChars) {
|
||||||
|
this.delimChars = delimChars;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEscapeChars() {
|
||||||
|
return escapeChars;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tokenizer setEscapeChars(String escapeChars) {
|
||||||
|
this.escapeChars = escapeChars;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWhiteChars() {
|
||||||
|
return whiteChars;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tokenizer setWhiteChars(String whiteChars) {
|
||||||
|
this.whiteChars = whiteChars;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Utility method which collects all tokens and returns an array of them.
|
||||||
|
* Use it for small length strings when parsing user input.
|
||||||
|
* @param withdelims if tru returns delimiters as well
|
||||||
|
*/
|
||||||
|
public String[] getTokens(boolean withdelims){
|
||||||
|
final ArrayList<String> buf=new ArrayList<String>();
|
||||||
|
final StringBuilder out=new StringBuilder();
|
||||||
|
boolean lastSkipped=false;
|
||||||
|
while(this.nextToken(out)){
|
||||||
|
String tok=out.toString();
|
||||||
|
out.setLength(0);
|
||||||
|
if(!withdelims && tok.length()==1 && isElementOf(tok.charAt(0),delimChars)!=-1){
|
||||||
|
if(lastSkipped) buf.add("");
|
||||||
|
lastSkipped=true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
buf.add(tok);
|
||||||
|
lastSkipped=false;
|
||||||
|
}
|
||||||
|
return buf.toArray(new String[buf.size()]);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Iterator<String> iterator() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return this.hasMoreTokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String next() {
|
||||||
|
return this.nextToken();
|
||||||
|
}
|
||||||
|
public static int isElementOf(char ch,String d){
|
||||||
|
if(d==null) return -1;
|
||||||
|
return d.indexOf(ch);
|
||||||
|
}
|
||||||
|
/**Returns the next token and updated offset.
|
||||||
|
* This is an inline tokenizer for text parsing and the workhorse of the class.
|
||||||
|
* It stops when it encounters a delimiter. It treats delimChars as tokens too.
|
||||||
|
* It advances the offset whenever it was able to move be it delimiter or not.
|
||||||
|
* We should not have to adjust it for repeated calls except for special cases.
|
||||||
|
* @param offset
|
||||||
|
* @param sets various char sets 0-delimiters,1-escape chars,3-white chars
|
||||||
|
* @param input input chars
|
||||||
|
* @param out value of the token
|
||||||
|
* @return offset after processing
|
||||||
|
*/
|
||||||
|
public static int nextToken(int offset,CharSequence input, StringBuilder out,String[] sets){
|
||||||
|
String delimChars=(sets!=null && sets.length>=1)?sets[0]:",:;=<>{}[]()";
|
||||||
|
String escapeChars=(sets!=null && sets.length>=2)?sets[1]:null;
|
||||||
|
String whiteChars=(sets!=null && sets.length>=3)?sets[2]:null;
|
||||||
|
int escChar=-1; // if not -1 then we are escaping
|
||||||
|
char lastChar=0;
|
||||||
|
char curChar=0;
|
||||||
|
int lastOffset=offset;
|
||||||
|
int isWhiteChar=-1;
|
||||||
|
int isDelimChar=-1;
|
||||||
|
boolean weakEscape=false;
|
||||||
|
int controlCount=0; // counts number of \\ to prevent shortcuit on even number
|
||||||
|
while(offset<input.length()){ // only scan until the end
|
||||||
|
lastChar=curChar;
|
||||||
|
curChar=input.charAt(offset++); // from here on offset is ahead
|
||||||
|
isWhiteChar=isElementOf(curChar,whiteChars);
|
||||||
|
isDelimChar=isElementOf(curChar,delimChars);
|
||||||
|
// determine if we should ignore testing for exit
|
||||||
|
int isEscapeChar=isElementOf(curChar,escapeChars);
|
||||||
|
controlCount=(lastChar=='\\')?controlCount+1:0; // control count counts number of \\ to
|
||||||
|
if((controlCount%2)==1){
|
||||||
|
isDelimChar=isEscapeChar=-1; // shortcircuit delimiting or escaping if prev char was \\ but only unevent number of times
|
||||||
|
}
|
||||||
|
if(escChar==-1){ // should we enter escaping
|
||||||
|
if(isEscapeChar!=-1){
|
||||||
|
// will enter escChar but only once
|
||||||
|
escChar=isEscapeChar;
|
||||||
|
}
|
||||||
|
}else{ // should we exit escaping
|
||||||
|
if(weakEscape==false) isDelimChar=-1; // shortcircuit delim signal if in escape mode and not weak
|
||||||
|
// exit back to normal if escape found second time
|
||||||
|
if(isEscapeChar==escChar){
|
||||||
|
// special rule:if oldchar==curchar and next is not delim or whitespace we ignore escape char
|
||||||
|
boolean isletter=offset<input.length() && !(isElementOf(input.charAt(offset),delimChars)!=-1 || isElementOf(input.charAt(offset),whiteChars)!=-1);
|
||||||
|
if(lastChar==curChar && isletter){
|
||||||
|
// we are special enter weak escaping (where delimiter is not ignored)
|
||||||
|
// this will correct spurios double quotes but will recover forgotter delimiters between two parts
|
||||||
|
// we stay in escape mode but listen for delims
|
||||||
|
weakEscape=true;
|
||||||
|
}else{
|
||||||
|
escChar=-1; // we are exiting escaping
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// emit chars and test for exit
|
||||||
|
if(escChar>=0 || isWhiteChar==-1 || isDelimChar!=-1){
|
||||||
|
// emit if escaping or if delimiter or not white char
|
||||||
|
out.append(curChar);
|
||||||
|
}
|
||||||
|
if(isDelimChar!=-1) break; // exit delimiter found
|
||||||
|
}
|
||||||
|
if(isDelimChar!=-1 && lastOffset<(offset-1)){
|
||||||
|
// fix end of out to not have a delimiter if it has any other string
|
||||||
|
offset-=1;out.setLength(out.length()-1);
|
||||||
|
}
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
/**Returns the next token and updated offset.
|
||||||
|
* An improved inline tokenizer using various rules to control delimiting, escaping and text swallowing.
|
||||||
|
* We supply an array of events or if none is provided a default delimiter event is constructed.
|
||||||
|
* After that the events are used to control tokenization. We enter a loop and feed the input to
|
||||||
|
* the events if one or more are armed or triggered (state >=0) we defer emiting chars to output until we determine what to do.
|
||||||
|
* For events that do escape we just defer until end of escape is detected, for delimit we return back and
|
||||||
|
* for supress we just swallow the input without emitting it.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
public static int nextToken(TokenizerRule state,int offset,CharSequence input, StringBuilder out){
|
||||||
|
int emitCount=0;
|
||||||
|
int oldOffset=offset;
|
||||||
|
while(offset<input.length()){
|
||||||
|
int st=state.consume(offset, input);
|
||||||
|
st=(st==TokenizerRule.DO_DEFER && offset==(input.length()))?TokenizerRule.DO_EMIT:st;
|
||||||
|
switch(st){
|
||||||
|
case TokenizerRule.DO_EMIT:
|
||||||
|
// we can emit what we got so far
|
||||||
|
if(oldOffset<=offset){
|
||||||
|
offset++;
|
||||||
|
out.append(input,oldOffset,offset);
|
||||||
|
emitCount+=(offset-oldOffset);
|
||||||
|
oldOffset=offset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TokenizerRule.DO_DEFER:
|
||||||
|
// we need to defer emitting
|
||||||
|
offset++;
|
||||||
|
break;
|
||||||
|
case TokenizerRule.DO_SKIP:
|
||||||
|
// we just skip over this input
|
||||||
|
offset++;
|
||||||
|
oldOffset=offset;
|
||||||
|
break;
|
||||||
|
case TokenizerRule.DO_EXITBEFORE:
|
||||||
|
if(emitCount>0){
|
||||||
|
offset-=(state.getSize()-1);
|
||||||
|
}
|
||||||
|
case TokenizerRule.DO_EXITAFTER:
|
||||||
|
if(emitCount==0 && oldOffset<=offset){
|
||||||
|
// if there is anything left
|
||||||
|
offset++;
|
||||||
|
out.append(input,oldOffset,offset);
|
||||||
|
emitCount+=(offset-oldOffset);
|
||||||
|
oldOffset=offset;
|
||||||
|
}
|
||||||
|
state.clear();
|
||||||
|
default:
|
||||||
|
return st>=0?st:offset;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.reliancy.dbo;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
public class TerminalTest {
|
||||||
|
public static class Maps extends DBO{
|
||||||
|
public static Field map_id=new Field("Map_id",Integer.class);
|
||||||
|
public static Field map_name=new Field("Map_name",String.class);
|
||||||
|
static{
|
||||||
|
Entity.publish(Maps.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Plain CRUD
|
||||||
|
* @throws IOException
|
||||||
|
* @throws SQLException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void connection() throws IOException, SQLException{
|
||||||
|
String url="jdbc:postgresql://postgres:Ramudin99@bigbang:5432/Test";
|
||||||
|
SQLTerminal t=new SQLTerminal(url);
|
||||||
|
try(Connection c=t.getConnection()){
|
||||||
|
System.out.println("Connection:"+c);
|
||||||
|
try (Statement stmt = c.createStatement()) {
|
||||||
|
// use stmt here
|
||||||
|
String sql = "SELECT * from \"dbo\".\"Maps\"";
|
||||||
|
try (ResultSet resultSet = stmt.executeQuery(sql)) {
|
||||||
|
// use resultSet here
|
||||||
|
while (resultSet.next()) {
|
||||||
|
System.out.println("ROw:"+resultSet.getInt("Map_id")+":"+resultSet.getString("Map_name"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.reliancy.jabba;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
/**
|
||||||
|
* Unit test for simple App.
|
||||||
|
*/
|
||||||
|
public class RouterTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Rigorous Test :-)
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldAnswerWithTrue()
|
||||||
|
{
|
||||||
|
Pattern p=Pattern.compile("(/hello)|(/hello2(/c))|(/hello3)");
|
||||||
|
Matcher m=p.matcher("/hello2/c");
|
||||||
|
if(m.matches()){
|
||||||
|
for(int i=0;i<m.groupCount();i++){
|
||||||
|
System.out.println(i+":"+m.group(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue( true );
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void initRouter()
|
||||||
|
{
|
||||||
|
//assertTrue( true );
|
||||||
|
System.out.println("Test router init...");
|
||||||
|
Router r=new Router();
|
||||||
|
RouterEndPoint rep=r.importEndPoints(r);
|
||||||
|
rep.compile();
|
||||||
|
//Matcher m=rep.match("GET","/helloPlain");
|
||||||
|
Matcher m=rep.match("GET","/hello3/45");
|
||||||
|
//Matcher m=rep.match("GET","/helloP");
|
||||||
|
if(m!=null){
|
||||||
|
HashMap<String,String> pms=new HashMap<>();
|
||||||
|
String rt=rep.evalMatcher(m,pms);
|
||||||
|
System.out.println(rt);
|
||||||
|
System.out.println(pms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.reliancy.rec;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class ObjTest {
|
||||||
|
/**
|
||||||
|
* Plain CRUD
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void crudVec() throws IOException
|
||||||
|
{
|
||||||
|
Obj o=new Obj();
|
||||||
|
Obj a=new Obj(true);
|
||||||
|
System.out.println("O1:"+o);
|
||||||
|
System.out.println("A1:"+a);
|
||||||
|
a.add(1).add("three");
|
||||||
|
o.add(1).add("three").set(new Slot("arr"),new String[]{"a","b","c"});
|
||||||
|
System.out.println("O2meta:"+o.isArray()+"/"+o.meta());
|
||||||
|
System.out.println("O2:"+o);
|
||||||
|
System.out.println("A2:"+a);
|
||||||
|
o.set(o.getSlot("car"),"bar");
|
||||||
|
System.out.println("O3:"+o);
|
||||||
|
StringBuilder json=new StringBuilder();
|
||||||
|
JSON.writes(o,json);
|
||||||
|
System.out.println("ENC:"+json);
|
||||||
|
Rec dec=JSON.reads(json);
|
||||||
|
System.out.println("DEC:"+dec);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Flask Template Example</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
|
||||||
|
<style type="text/css">
|
||||||
|
.container {
|
||||||
|
max-width: 500px;
|
||||||
|
padding-top: 100px;
|
||||||
|
}
|
||||||
|
h2 {color: red;}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h2>This is part of my base template</h2>
|
||||||
|
<br>
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
<br>
|
||||||
|
<h2>This is part of my base template</h2>
|
||||||
|
</div>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
Reference in New Issue
Block a user