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