From a8e37f705d5f94796bd082ea8bb64381a4dc6897 Mon Sep 17 00:00:00 2001 From: Amer Agovic Date: Thu, 22 Sep 2022 16:26:23 -0500 Subject: [PATCH] prep to push --- .settings/org.eclipse.buildship.core.prefs | 2 +- LICENCE | 56 +++ README.md | 17 +- build.gradle | 39 ++- pom.xml | 176 +++------- src/main/java/com/reliancy/dbo/Action.java | 7 + src/main/java/com/reliancy/dbo/Bag.java | 167 +++++++++ src/main/java/com/reliancy/dbo/Check.java | 22 ++ src/main/java/com/reliancy/dbo/DBO.java | 7 + src/main/java/com/reliancy/dbo/Entity.java | 12 +- src/main/java/com/reliancy/dbo/Field.java | 7 + .../java/com/reliancy/dbo/FieldSlice.java | 7 + src/main/java/com/reliancy/dbo/SQL.java | 11 +- .../java/com/reliancy/dbo/SQLCleaner.java | 7 + src/main/java/com/reliancy/dbo/SQLReader.java | 9 +- .../java/com/reliancy/dbo/SQLTerminal.java | 7 + src/main/java/com/reliancy/dbo/SQLWriter.java | 18 +- .../java/com/reliancy/dbo/SiphonIterator.java | 7 + src/main/java/com/reliancy/dbo/Terminal.java | 9 +- src/main/java/com/reliancy/jabba/App.java | 126 +++++++ .../java/com/reliancy/jabba/AppSession.java | 34 +- .../com/reliancy/jabba/AppSessionFilter.java | 23 +- .../java/com/reliancy/jabba/CallSession.java | 11 +- src/main/java/com/reliancy/jabba/Config.java | 35 +- .../java/com/reliancy/jabba/EndPoint.java | 7 + .../java/com/reliancy/jabba/FileConfig.java | 54 +++ .../java/com/reliancy/jabba/FileServer.java | 14 +- src/main/java/com/reliancy/jabba/HTTP.java | 7 + .../java/com/reliancy/jabba/JettyApp.java | 330 ++++++++++++++++++ .../com/reliancy/jabba/MethodDecorator.java | 44 +++ .../com/reliancy/jabba/MethodEndPoint.java | 48 ++- src/main/java/com/reliancy/jabba/Path.java | 7 + .../java/com/reliancy/jabba/Processor.java | 21 +- src/main/java/com/reliancy/jabba/Request.java | 10 +- .../java/com/reliancy/jabba/Response.java | 9 + .../com/reliancy/jabba/ResponseEncoder.java | 13 +- src/main/java/com/reliancy/jabba/Route.java | 11 - .../com/reliancy/jabba/RouteDetector.java | 112 ++++++ src/main/java/com/reliancy/jabba/Routed.java | 19 + ...outerEndPoint.java => RoutedEndPoint.java} | 100 +++--- src/main/java/com/reliancy/jabba/Router.java | 231 ------------ src/main/java/com/reliancy/jabba/Session.java | 7 + .../reliancy/jabba/sec/NeedCredentials.java | 20 ++ .../com/reliancy/jabba/sec/NotAuthentic.java | 18 + .../com/reliancy/jabba/sec/NotPermitted.java | 19 + .../com/reliancy/jabba/sec/Securable.java | 37 ++ .../java/com/reliancy/jabba/sec/Secured.java | 20 ++ .../com/reliancy/jabba/sec/SecurityActor.java | 22 ++ .../sec}/SecurityPermit.java | 12 +- .../reliancy/jabba/sec/SecurityPolicy.java | 224 ++++++++++++ .../reliancy/jabba/sec/SecurityProtocol.java | 108 ++++++ .../com/reliancy/jabba/sec/SecurityStore.java | 41 +++ .../reliancy/jabba/sec/plain/PlainActor.java | 46 +++ .../reliancy/jabba/sec/plain/PlainPermit.java | 76 ++++ .../jabba/sec/plain/PlainSecurable.java | 109 ++++++ .../jabba/sec/plain/PlainSecurityStore.java | 198 +++++++++++ .../java/com/reliancy/jabba/ui/Feedback.java | 46 +++ .../com/reliancy/jabba/ui/FeedbackLine.java | 44 +++ src/main/java/com/reliancy/jabba/ui/Menu.java | 76 ++++ .../java/com/reliancy/jabba/ui/MenuItem.java | 53 +++ .../java/com/reliancy/jabba/ui/Rendering.java | 77 ++++ .../reliancy/{util => jabba/ui}/Template.java | 42 ++- .../com/reliancy/jabbasec/NotAuthentic.java | 11 - .../com/reliancy/jabbasec/NotPermitted.java | 12 - .../java/com/reliancy/jabbasec/Securable.java | 20 -- .../com/reliancy/jabbasec/SecurityActor.java | 9 - .../com/reliancy/jabbasec/SecurityPolicy.java | 125 ------- .../reliancy/jabbasec/SecurityProtocol.java | 42 --- .../java/com/reliancy/rec/DecoderSink.java | 8 + src/main/java/com/reliancy/rec/Hdr.java | 7 + src/main/java/com/reliancy/rec/JSON.java | 7 + .../java/com/reliancy/rec/JSONDecoder.java | 14 +- .../java/com/reliancy/rec/JSONEncoder.java | 7 + src/main/java/com/reliancy/rec/Obj.java | 7 + src/main/java/com/reliancy/rec/Rec.java | 7 + src/main/java/com/reliancy/rec/Slot.java | 7 + .../java/com/reliancy/rec/TextDecoder.java | 12 +- src/main/java/com/reliancy/rec/Vec.java | 7 + .../java/com/reliancy/util/CodeException.java | 138 ++++++++ src/main/java/com/reliancy/util/Handy.java | 52 ++- src/main/java/com/reliancy/util/Path.java | 16 +- .../java/com/reliancy/util/Resources.java | 8 + .../java/com/reliancy/util/ResultCode.java | 131 +++++++ .../java/com/reliancy/util/Tokenizer.java | 9 +- src/main/resources/templates/error.hbs | 18 + src/main/resources/templates/login.hbs | 26 +- .../java/com/reliancy/dbo/TerminalTest.java | 33 +- .../java/com/reliancy/jabba/RouterTest.java | 12 +- src/test/java/com/reliancy/rec/ObjTest.java | 13 +- var/favicon.ico | Bin 32038 -> 0 bytes 90 files changed, 3159 insertions(+), 747 deletions(-) create mode 100644 LICENCE create mode 100644 src/main/java/com/reliancy/dbo/Bag.java create mode 100644 src/main/java/com/reliancy/jabba/App.java create mode 100644 src/main/java/com/reliancy/jabba/FileConfig.java create mode 100644 src/main/java/com/reliancy/jabba/JettyApp.java create mode 100644 src/main/java/com/reliancy/jabba/MethodDecorator.java delete mode 100644 src/main/java/com/reliancy/jabba/Route.java create mode 100644 src/main/java/com/reliancy/jabba/RouteDetector.java create mode 100644 src/main/java/com/reliancy/jabba/Routed.java rename src/main/java/com/reliancy/jabba/{RouterEndPoint.java => RoutedEndPoint.java} (55%) delete mode 100644 src/main/java/com/reliancy/jabba/Router.java create mode 100644 src/main/java/com/reliancy/jabba/sec/NeedCredentials.java create mode 100644 src/main/java/com/reliancy/jabba/sec/NotAuthentic.java create mode 100644 src/main/java/com/reliancy/jabba/sec/NotPermitted.java create mode 100644 src/main/java/com/reliancy/jabba/sec/Securable.java create mode 100644 src/main/java/com/reliancy/jabba/sec/Secured.java create mode 100644 src/main/java/com/reliancy/jabba/sec/SecurityActor.java rename src/main/java/com/reliancy/{jabbasec => jabba/sec}/SecurityPermit.java (59%) create mode 100644 src/main/java/com/reliancy/jabba/sec/SecurityPolicy.java create mode 100644 src/main/java/com/reliancy/jabba/sec/SecurityProtocol.java create mode 100644 src/main/java/com/reliancy/jabba/sec/SecurityStore.java create mode 100644 src/main/java/com/reliancy/jabba/sec/plain/PlainActor.java create mode 100644 src/main/java/com/reliancy/jabba/sec/plain/PlainPermit.java create mode 100644 src/main/java/com/reliancy/jabba/sec/plain/PlainSecurable.java create mode 100644 src/main/java/com/reliancy/jabba/sec/plain/PlainSecurityStore.java create mode 100644 src/main/java/com/reliancy/jabba/ui/Feedback.java create mode 100644 src/main/java/com/reliancy/jabba/ui/FeedbackLine.java create mode 100644 src/main/java/com/reliancy/jabba/ui/Menu.java create mode 100644 src/main/java/com/reliancy/jabba/ui/MenuItem.java create mode 100644 src/main/java/com/reliancy/jabba/ui/Rendering.java rename src/main/java/com/reliancy/{util => jabba/ui}/Template.java (72%) delete mode 100644 src/main/java/com/reliancy/jabbasec/NotAuthentic.java delete mode 100644 src/main/java/com/reliancy/jabbasec/NotPermitted.java delete mode 100644 src/main/java/com/reliancy/jabbasec/Securable.java delete mode 100644 src/main/java/com/reliancy/jabbasec/SecurityActor.java delete mode 100644 src/main/java/com/reliancy/jabbasec/SecurityPolicy.java delete mode 100644 src/main/java/com/reliancy/jabbasec/SecurityProtocol.java create mode 100644 src/main/java/com/reliancy/util/CodeException.java create mode 100644 src/main/java/com/reliancy/util/ResultCode.java create mode 100644 src/main/resources/templates/error.hbs delete mode 100644 var/favicon.ico diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs index 9545588..4ce123b 100644 --- a/.settings/org.eclipse.buildship.core.prefs +++ b/.settings/org.eclipse.buildship.core.prefs @@ -1,7 +1,7 @@ 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.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(7.0)) connection.project.dir= eclipse.preferences.version=1 gradle.user.home= diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..075e90a --- /dev/null +++ b/LICENCE @@ -0,0 +1,56 @@ +GNU LESSER GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. + +0. Additional Definitions. +As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License. + +“The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. + +An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. + +A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”. + +The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. + +The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. + +1. Exception to Section 3 of the GNU GPL. +You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. + +2. Conveying Modified Versions. +If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: + +a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or +b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. +3. Object Code Incorporating Material from Library Header Files. +The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: + +a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. +b) Accompany the object code with a copy of the GNU GPL and this license document. +4. Combined Works. +You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: + +a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. +b) Accompany the Combined Work with a copy of the GNU GPL and this license document. +c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. +d) Do one of the following: +0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. +1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. +e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) +5. Combined Libraries. +You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: + +a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. +b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. +6. Revised Versions of the GNU Lesser General Public License. +The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. \ No newline at end of file diff --git a/README.md b/README.md index 40f9451..6e727d2 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,22 @@ # 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. +Jabba is a java library that gets its inspiration from Python 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 +* running a continouse server via: gradle --watch-fs -t runServer, then work on code ## Things Left to Do -* ~~Complete support for demarshalling and marshalling of objects to java methods~~ on 10/4/2021 +* ~~Complete support for demarshalling and marshalling of objects to java methods~~ * ~~Session middleware~~ -* Auth middleware supporting basic and digest, an security entities +* ~~Auth middleware supporting basic and digest, and security entities~~ * ~~Static file serving~~ * ~~Templating like jinja~~ -* Database layer or serial/deserial system like SQL Alchemy \ No newline at end of file +* ~~Homepage with login templates~~ +* ~~Error page~~ +* ~~Menu handling~~ +* ~~Database layer or serial/deserial system like SQL Alchemy~~ + +With above things complete we ~~will~~ have a library that can be used for new webapps. +Don't have a profile page (which goes into app templates) and the dbo layer is basic not like sql alchemy but mostly things are in place. At this point we could use jabba to spawn new apps. +Now I could prepare it for github and for maven central. \ No newline at end of file diff --git a/build.gradle b/build.gradle index 312dd4f..02d91c8 100644 --- a/build.gradle +++ b/build.gradle @@ -11,8 +11,11 @@ apply plugin: 'application' apply plugin: 'maven-publish' group='com.reliancy' +mainClassName = group+'.'+name+'.JettyApp' version = '0.1' -mainClassName = group+'.'+name+'.Router' +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + System.out.println("group:"+group); System.out.println("name:"+name); System.out.println("version:"+version); @@ -22,17 +25,34 @@ repositories { mavenLocal() mavenCentral() } -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + pom { + licenses { + license { + name = 'GNU Lesser General Public License, Version 3.0' + url = 'https://www.gnu.org/licenses/lgpl-3.0.txt' + } + } + } + } + } +} +javadoc { + source = sourceSets.main.allJava + //classpath = configurations.compile +} 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.github.jknack:handlebars:4.2.1' - implementation 'com.h2database:h2:1.4.200' + implementation 'com.github.jknack:handlebars:4.3.0' + implementation 'com.h2database:h2:2.1.214' // https://mvnrepository.com/artifact/org.postgresql/postgresql - implementation 'org.postgresql:postgresql:42.3.1' + implementation 'org.postgresql:postgresql:42.5.0' // https://mvnrepository.com/artifact/com.zaxxer/HikariCP implementation 'com.zaxxer:HikariCP:5.0.0' @@ -180,13 +200,6 @@ task runServer{ //args "arg1", "arg2" */ } -publishing { - publications { - mavenJava(MavenPublication) { - from components.java - } - } -} eclipse{ classpath { defaultOutputDir = file("build") ///default diff --git a/pom.xml b/pom.xml index c30db1a..630ef8a 100644 --- a/pom.xml +++ b/pom.xml @@ -1,145 +1,57 @@ - - + + + + + + 4.0.0 - - com.reliancy.jabba + com.reliancy jabba - 1.0 - jar - jabba - http://www.reliancy.com - - - UTF-8 - 1.8 - 1.8 - - + 0.1 + + + The Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + - - junit - junit - 4.11 - test - org.eclipse.jetty jetty-server - 11.0.7 + 12.0.0.alpha1 + runtime - org.eclipse.jetty - jetty-servlet - 11.0.1 + org.slf4j + slf4j-simple + 2.0.0-alpha0 + runtime - com.hubspot.jinjava - jinjava - 2.5.10 - + com.github.jknack + handlebars + 4.3.0 + runtime + + + com.h2database + h2 + 2.1.214 + runtime + + + org.postgresql + postgresql + 42.5.0 + runtime + + + com.zaxxer + HikariCP + 5.0.0 + runtime + - - - - - org.apache.maven.plugins - maven-dependency-plugin - 3.1.2 - - - copy-dependencies - package - - copy-dependencies - - - - - ${project.build.directory}/lib - - - - - org.apache.maven.plugins - maven-assembly-plugin - 2.4.1 - - - - jar-with-dependencies - - - - - com.reliancy.jabba.Router - - - - - - - make-assembly - - package - - single - - - - - - - - - org.codehaus.mojo - exec-maven-plugin - 3.0.0 - - com.reliancy.jabba.Router - - - - - maven-clean-plugin - 3.1.0 - - - - maven-resources-plugin - 3.0.2 - - - maven-compiler-plugin - 3.8.0 - - - maven-surefire-plugin - 2.22.1 - - - maven-jar-plugin - 3.0.2 - - - maven-install-plugin - 2.5.2 - - - maven-deploy-plugin - 2.8.2 - - - - maven-site-plugin - 3.7.1 - - - maven-project-info-reports-plugin - 3.0.0 - - - - diff --git a/src/main/java/com/reliancy/dbo/Action.java b/src/main/java/com/reliancy/dbo/Action.java index f7e19bb..5b4e7ab 100644 --- a/src/main/java/com/reliancy/dbo/Action.java +++ b/src/main/java/com/reliancy/dbo/Action.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.dbo; import java.io.IOException; diff --git a/src/main/java/com/reliancy/dbo/Bag.java b/src/main/java/com/reliancy/dbo/Bag.java new file mode 100644 index 0000000..b473dd8 --- /dev/null +++ b/src/main/java/com/reliancy/dbo/Bag.java @@ -0,0 +1,167 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ + +package com.reliancy.dbo; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.ListIterator; +import java.util.Observable; + +/** A more or less virtual collection of items. + * this object is a suitable holder of resultsets. it will be overridable so + * we can create specialized virtual holders that use backends. by itself it will + * implement in memory list. + * also this class is an observable and we can monitor update to it. + */ +public class Bag extends Observable implements Collection{ + /** event to send to observers. */ + public static final class BagChanged{ + public static final int ADD=0; + public static final int REMOVE=1; + public static final int ACCESS=2; + public static final int POST_LOAD=3; + public static final int PRE_SAVE=4; + final Bag bag; + final int operation; + final Object[] arguments; + public BagChanged(Bag p,int op,Object ... args){ + bag=p; + operation=op; + arguments=args; + } + public Bag getBag() { + return bag; + } + public int getOperation() { + return operation; + } + public Object[] getArguments() { + return arguments; + } + } + final ArrayList items=new ArrayList<>(); + + public Bag(){ + } + public Bag(Iterable o){ + this(o.iterator()); + } + public Bag(Iterator o){ + while(o.hasNext()) add(o.next()); + } + @Override + public int size() { + return items.size(); + } + + @Override + public boolean isEmpty() { + return size()==0; + } + + @Override + public boolean contains(Object o) { + final Iterator it=iterator(); + while(it.hasNext()){ + final E e=it.next(); + if(e!=null && o!=null && e.equals(o)) return true; + else if(e==o) return true; + } + return false; + } + @Override + public boolean containsAll(Collection c) { + for (Object e : c) if (!contains(e)) return false; + return true; + } + public ListIterator listIterator(){ + return listIterator(0); + } + public ListIterator listIterator(int offset){ + return items.listIterator(offset); + } + @Override + public Iterator iterator() { + return items.iterator(); + } + + @Override + public Object[] toArray() { + return toArray(new Object[size()]); + } + + @Override + public T[] toArray(T[] a) { + return items.toArray(a); + } + + @Override + public boolean add(E e) { + if(items.contains(e)) return true; + if(countObservers()>0){ + BagChanged evt=new Bag.BagChanged<>(this,BagChanged.ADD,e); + setChanged(); + notifyObservers(evt); + } + return items.add(e); + } + public Bag append(E e){ + add(e); + return this; + } + @Override + public boolean remove(Object o) { + if(!contains(o)) return false; + if(countObservers()>0){ + BagChanged evt=new Bag.BagChanged<>(this,BagChanged.REMOVE,o); + setChanged(); + notifyObservers(evt); + } + return items.remove(o); + } + + @Override + public boolean addAll(Collection c) { + if(countObservers()>0){ + BagChanged evt=new Bag.BagChanged<>(this,BagChanged.ADD,c.toArray()); + setChanged(); + notifyObservers(evt); + } + if(c==null || c.size()==0) return false; + c.forEach(e->{this.append(e);}); + return true; + } + + @Override + public boolean removeAll(Collection c) { + if(countObservers()>0){ + BagChanged evt=new Bag.BagChanged<>(this,BagChanged.REMOVE,c!=null?c.toArray():null); + setChanged(); + notifyObservers(evt); + } + if(c!=null){ + return items.removeAll(c); + }else{ + items.clear(); + return true; + } + } + + @Override + public boolean retainAll(Collection c) { + return items.retainAll(c); + } + + @Override + public void clear() { + removeAll(null); + } + +} diff --git a/src/main/java/com/reliancy/dbo/Check.java b/src/main/java/com/reliancy/dbo/Check.java index 23867fc..a390f14 100644 --- a/src/main/java/com/reliancy/dbo/Check.java +++ b/src/main/java/com/reliancy/dbo/Check.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.dbo; import java.util.Iterator; @@ -9,78 +16,93 @@ public class Check implements Iterable { public static abstract class Op{ public abstract boolean met(Check c,Object val); } + /** logical AND operation. */ public static Op AND=new Op(){ public String toString(){return "AND";} public boolean met(Check c,Object val){ return true; } }; + /** logical OR operation. */ public static Op OR=new Op(){ public String toString(){return "OR";} public boolean met(Check c,Object val){ return true; } }; + /** logical NOT operation. */ public static Op NOT=new Op(){ public String toString(){return "NOT";} public boolean met(Check c,Object val){ return true; } }; + /** arithmetic equal test. */ public static Op EQ=new Op(){ public String toString(){return "=";} public boolean met(Check c,Object val){ return true; } }; + /** arithmetic negated equal test. */ public static Op NEQ=new Op(){ public String toString(){return "<>";} public boolean met(Check c,Object val){ return true; } }; + /** greater than check. */ public static Op GT=new Op(){ public String toString(){return ">";} public boolean met(Check c,Object val){ return true; } }; + /** greater than or equal check. */ public static Op GTE=new Op(){ public String toString(){return ">=";} public boolean met(Check c,Object val){ return true; } }; + /** less than check. */ public static Op LT=new Op(){ public String toString(){return "<";} public boolean met(Check c,Object val){ return true; } }; + /** less than or equal check. */ public static Op LTE=new Op(){ public String toString(){return "<=";} public boolean met(Check c,Object val){ return true; } }; + /** like check case insensitive. */ public static Op LIKE=new Op(){ public String toString(){return "LIKE";} public boolean met(Check c,Object val){ return true; } }; + /** set membership check. */ public static Op IN=new Op(){ public String toString(){return "IN";} public boolean met(Check c,Object val){ return true; } }; + /** negated set membership check. */ public static Op NOT_IN=new Op(){ public String toString(){return "NOT IN";} public boolean met(Check c,Object val){ return true; } }; + /** iterator over checks. + * + */ public static class CheckIterator implements Iterator{ final Check root; Check cur; diff --git a/src/main/java/com/reliancy/dbo/DBO.java b/src/main/java/com/reliancy/dbo/DBO.java index cfc6fa4..d992f26 100644 --- a/src/main/java/com/reliancy/dbo/DBO.java +++ b/src/main/java/com/reliancy/dbo/DBO.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.dbo; import java.io.IOException; diff --git a/src/main/java/com/reliancy/dbo/Entity.java b/src/main/java/com/reliancy/dbo/Entity.java index 7c28de6..cee3277 100644 --- a/src/main/java/com/reliancy/dbo/Entity.java +++ b/src/main/java/com/reliancy/dbo/Entity.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.dbo; import java.util.HashMap; @@ -53,15 +60,16 @@ public class Entity extends Hdr{ * @param cls * @return */ + @SuppressWarnings("unchecked") public static final Entity publish(Class cls){ Entity ret=registry.get(cls.getSimpleName()); if(ret!=null) return ret; //System.out.println("Analyzing:"+cls); - Class base=cls.getSuperclass(); + Class base=cls.getSuperclass(); Entity base_ent=null; int position0=0; if(base!=null && base!=DBO.class){ - base_ent=publish(base); + base_ent=publish((Class)base); position0=base_ent.count(); } java.lang.reflect.Field[] declaredFields = cls.getDeclaredFields(); diff --git a/src/main/java/com/reliancy/dbo/Field.java b/src/main/java/com/reliancy/dbo/Field.java index 1afff90..ba66c43 100644 --- a/src/main/java/com/reliancy/dbo/Field.java +++ b/src/main/java/com/reliancy/dbo/Field.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.dbo; import java.math.BigDecimal; diff --git a/src/main/java/com/reliancy/dbo/FieldSlice.java b/src/main/java/com/reliancy/dbo/FieldSlice.java index 6f1d2b7..4a07a35 100644 --- a/src/main/java/com/reliancy/dbo/FieldSlice.java +++ b/src/main/java/com/reliancy/dbo/FieldSlice.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.dbo; import java.util.Iterator; import java.util.List; diff --git a/src/main/java/com/reliancy/dbo/SQL.java b/src/main/java/com/reliancy/dbo/SQL.java index 89baa8a..c781dc4 100644 --- a/src/main/java/com/reliancy/dbo/SQL.java +++ b/src/main/java/com/reliancy/dbo/SQL.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.dbo; import java.util.HashMap; @@ -207,8 +214,8 @@ public final class SQL implements Appendable{ /** fills check values from dbo record where equal and not-equal are used. * we place this method here to be as close as possible to the one which generates the sql code. * check and check_import must be in synch. - * @param filter - * @param params + * @param filter set of checks + * @param rec record to check */ public final void check_import(Check filter,DBO rec) { if(filter.isLeaf()){ diff --git a/src/main/java/com/reliancy/dbo/SQLCleaner.java b/src/main/java/com/reliancy/dbo/SQLCleaner.java index 59fc8e7..a03bcd8 100644 --- a/src/main/java/com/reliancy/dbo/SQLCleaner.java +++ b/src/main/java/com/reliancy/dbo/SQLCleaner.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.dbo; import java.io.Closeable; diff --git a/src/main/java/com/reliancy/dbo/SQLReader.java b/src/main/java/com/reliancy/dbo/SQLReader.java index 445a05e..7a391a3 100644 --- a/src/main/java/com/reliancy/dbo/SQLReader.java +++ b/src/main/java/com/reliancy/dbo/SQLReader.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.dbo; import java.io.IOException; @@ -11,7 +18,7 @@ import com.reliancy.dbo.Action.Load; /** SQLIterator will delay closing a connection and will iterate over result set. - * + * TODO: no support for orderby yet */ public class SQLReader implements SiphonIterator{ protected final Entity entity; diff --git a/src/main/java/com/reliancy/dbo/SQLTerminal.java b/src/main/java/com/reliancy/dbo/SQLTerminal.java index df21078..e978151 100644 --- a/src/main/java/com/reliancy/dbo/SQLTerminal.java +++ b/src/main/java/com/reliancy/dbo/SQLTerminal.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.dbo; import java.io.IOException; diff --git a/src/main/java/com/reliancy/dbo/SQLWriter.java b/src/main/java/com/reliancy/dbo/SQLWriter.java index 38fa709..2ad0304 100644 --- a/src/main/java/com/reliancy/dbo/SQLWriter.java +++ b/src/main/java/com/reliancy/dbo/SQLWriter.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.dbo; import java.io.Closeable; @@ -172,7 +179,14 @@ public class SQLWriter implements Closeable{ if(rec.getStatus()==DBO.Status.USED){ stmt=updateStmt; // update has a pk condition for sure - stmt.setObject(supplied.size()+1,pk.get(rec,null),terminal.getTypeId(pk.getType(),pk.getTypeParams())); + Object pkval=pk.get(rec,null); + if(Handy.isEmpty(pkval)) throw new SQLException("Used object with empty PK"); + //System.out.println("UPDT:"+stmt+"/"+pkval); + stmt.setObject( + supplied.size()+1, + pkval, + terminal.getTypeId(pk.getType(),pk.getTypeParams()) + ); } if(stmt==null) return false; // copy values @@ -199,7 +213,7 @@ public class SQLWriter implements Closeable{ } } } - } + }else if(rec.getStatus()==DBO.Status.USED){ this.itemsUpdated+=ucode; } diff --git a/src/main/java/com/reliancy/dbo/SiphonIterator.java b/src/main/java/com/reliancy/dbo/SiphonIterator.java index 8f0276e..74c38c1 100644 --- a/src/main/java/com/reliancy/dbo/SiphonIterator.java +++ b/src/main/java/com/reliancy/dbo/SiphonIterator.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.dbo; import java.io.Closeable; diff --git a/src/main/java/com/reliancy/dbo/Terminal.java b/src/main/java/com/reliancy/dbo/Terminal.java index 8154726..7602777 100644 --- a/src/main/java/com/reliancy/dbo/Terminal.java +++ b/src/main/java/com/reliancy/dbo/Terminal.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.dbo; import java.io.IOException; @@ -29,7 +36,7 @@ public interface Terminal { Entity ent=Entity.recall(cls); String sig="/"+ent.getName()+"/load"; try(Action act=begin(sig).load(ent).limit(1).if_pk(id).execute()){ - return (T)act.first(); + return cls.cast(act.first()); } } public default DBO load(Entity ent,Object...id) throws IOException { diff --git a/src/main/java/com/reliancy/jabba/App.java b/src/main/java/com/reliancy/jabba/App.java new file mode 100644 index 0000000..4063689 --- /dev/null +++ b/src/main/java/com/reliancy/jabba/App.java @@ -0,0 +1,126 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ + +package com.reliancy.jabba; + +import java.io.IOException; + +import com.reliancy.jabba.sec.SecurityPolicy; +import com.reliancy.util.CodeException; +import com.reliancy.util.ResultCode; + +/** Base Application class from where specific launchers derive. + * Derived classes will usually bring in jetty or tomcat or some other launch ability. + */ +public abstract class App extends Processor{ + public static int ERR_NOCONFIG=ResultCode.defineFailure(0x01,App.class,"config missing. provide at least empty one."); + public static int ERR_NOTCLOSED=ResultCode.defineFailure(0x02,App.class,"unbalanced call. resource called twice:${resource}"); + protected Processor first=null; + protected Processor last=null; + protected RoutedEndPoint router=null; + protected SecurityPolicy policy=null; + + public App(String id) { + super(id); + } + public void before(Request request,Response response) throws IOException{ + } + public void after(Request request,Response response) throws IOException{ + } + public void serve(Request req,Response resp) throws IOException{ + if(first!=null) first.process(req, resp); + if(router!=null) router.process(req,resp); + } + public T addProcessor(T m){ + if(first==null){ + last=first=m; + }else{ + last.next=m; + } + while(last.next!=null) last=last.next; + return m; + } + 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 RoutedEndPoint getRouter() { + return router; + } + public void setRouter(RoutedEndPoint router) { + this.router = router; + } + public void run(Config conf) throws Exception { + try{ + begin(conf); + work(); + }finally{ + end(); + } + } + @Override + public void begin(Config conf) throws Exception{ + if(config!=null) throw new CodeException(ERR_NOTCLOSED).put("resource","Router.begin()"); + if(conf==null) throw new CodeException(ERR_NOCONFIG); + config=conf; + for(Processor p=first;p!=null;p=p.getNext()){ + p.begin(config); + } + if(router!=null) router.begin(config); + } + @Override + public void end() throws Exception{ + if(router!=null) router.end(); + for(Processor p=first;p!=null;p=p.getNext()){ + p.end(); + } + super.end(); + log().info("stopping app:"+getId()); + } + public AppSessionFilter addAppSession(){ + return addProcessor(new AppSessionFilter(this)); + } + public AppSessionFilter addAppSession(AppSession.Factory f){ + return addProcessor(new AppSessionFilter(this,f)); + } + public SecurityPolicy setSecurityPolicy(SecurityPolicy secpol){ + if(secpol==policy) return secpol; + if(policy!=null){ + MethodDecorator.retract(policy); + removeProcessor(policy); + } + policy=secpol; + if(policy!=null){ + addProcessor(policy); + MethodDecorator.publish(policy); // register security policy as decorator factory + } + return secpol; + } + public SecurityPolicy getSecurityPolicy(){ + return policy; + } +} diff --git a/src/main/java/com/reliancy/jabba/AppSession.java b/src/main/java/com/reliancy/jabba/AppSession.java index 8d86930..92aa5e4 100644 --- a/src/main/java/com/reliancy/jabba/AppSession.java +++ b/src/main/java/com/reliancy/jabba/AppSession.java @@ -1,20 +1,33 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.jabba; import java.util.HashMap; -import com.reliancy.jabbasec.SecurityActor; +import com.reliancy.jabba.sec.SecurityActor; +import com.reliancy.jabba.ui.Feedback; public class AppSession implements Session{ + public static interface Factory{ + AppSession create(String id,App app); + } final String id; - final HashMap values; + final App app; + final HashMap values=new HashMap<>(); long timeCreated; long lastActive; long maxAge; SecurityActor user; - - public AppSession(String id){ + Feedback feedback; + + public AppSession(String id,App app){ this.id=id; - values=new HashMap<>(); + this.app=app; lastActive=timeCreated=System.currentTimeMillis(); maxAge=1000*60*15; } @@ -27,6 +40,9 @@ public class AppSession implements Session{ public Object getValue(String key) { return values.get(key); } + public App getApp(){ + return app; + } public long getTimeInactive(){ return System.currentTimeMillis()-lastActive; } @@ -64,4 +80,12 @@ public class AppSession implements Session{ public void setUser(SecurityActor user){ this.user=user; } + public static AppSession getInstance() { + CallSession ss=CallSession.getInstance(); + return ss!=null?(AppSession)ss.getAppSession():null; + } + public Feedback getFeedback() { + if(feedback==null) feedback=new Feedback(); + return feedback; + } } diff --git a/src/main/java/com/reliancy/jabba/AppSessionFilter.java b/src/main/java/com/reliancy/jabba/AppSessionFilter.java index 279bfa4..7ef6c8c 100644 --- a/src/main/java/com/reliancy/jabba/AppSessionFilter.java +++ b/src/main/java/com/reliancy/jabba/AppSessionFilter.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.jabba; import java.io.IOException; @@ -9,8 +16,16 @@ import java.util.UUID; */ public class AppSessionFilter extends Processor{ public static final String KEY_NAME="jbssid"; - public AppSessionFilter() { - super(AppSessionFilter.class.getSimpleName().toLowerCase()); + AppSession.Factory factory; + App app; + public AppSessionFilter(App a) { + this(a,null); + } + public AppSessionFilter(App a,AppSession.Factory f) { + super(AppSessionFilter.class.getSimpleName()); + app=a; + if(f==null) f=(id,app)->new AppSession(id, app); + factory=f; } @Override public void before(Request request, Response response) throws IOException { @@ -23,7 +38,7 @@ public class AppSessionFilter extends Processor{ if(ss!=null){ if(ss.isExpired()){ // this app sessin expired - create a new one - ss=new AppSession(ssid); + ss=factory.create(ssid,app); AppSession.setInstance(ssid, ss); }else{ // this session is good @@ -31,7 +46,7 @@ public class AppSessionFilter extends Processor{ } }else{ // no session available - ss=new AppSession(ssid); + ss=factory.create(ssid,app); AppSession.setInstance(ssid, ss); } CallSession css=CallSession.getInstance(); diff --git a/src/main/java/com/reliancy/jabba/CallSession.java b/src/main/java/com/reliancy/jabba/CallSession.java index 80eb980..cef571f 100644 --- a/src/main/java/com/reliancy/jabba/CallSession.java +++ b/src/main/java/com/reliancy/jabba/CallSession.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.jabba; import java.util.ArrayList; @@ -71,11 +78,11 @@ public class CallSession implements Session{ int len=callers.size(); return len>0?callers.get(len-1):null; } + public static ThreadLocal instance=new ThreadLocal<>(); /** * Will return current session given the call stack. - * @return + * @return thread local call session */ - public static ThreadLocal instance=new ThreadLocal<>(); public static CallSession getInstance(){ CallSession ret=instance.get(); if(ret==null) instance.set(ret=new CallSession()); diff --git a/src/main/java/com/reliancy/jabba/Config.java b/src/main/java/com/reliancy/jabba/Config.java index 48aef79..51844fa 100644 --- a/src/main/java/com/reliancy/jabba/Config.java +++ b/src/main/java/com/reliancy/jabba/Config.java @@ -1,9 +1,40 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.jabba; +import org.slf4j.Logger; public interface Config { + public static class Property { + + final String name; + final Class typ; + public Property(String name,Class typ){ + this.name=name; + this.typ=typ; + } + public String getName(){return name;} + public Class getTyp(){return typ;} + public V get(Config store,V def){ + return store.getProperty(this,def); + } + public V get(Config store){ + return get(store,null); + } + public void set(Config store,V val){ + store.setProperty(this, val); + } + } + public static final Property LOG_LEVEL=new Property("LOG_LEVEL",String.class); + public static final Property LOGGER=new Property("LOGGER",Logger.class); + public void load(); public void save(); public String getId(); - public Object getProperty(String key,Object def); - public Config setProperty(String key,Object val); + public Config setProperty(Property key,T val); + public T getProperty(Property key,T def); } diff --git a/src/main/java/com/reliancy/jabba/EndPoint.java b/src/main/java/com/reliancy/jabba/EndPoint.java index 8e87665..501cad2 100644 --- a/src/main/java/com/reliancy/jabba/EndPoint.java +++ b/src/main/java/com/reliancy/jabba/EndPoint.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.jabba; import java.io.IOException; diff --git a/src/main/java/com/reliancy/jabba/FileConfig.java b/src/main/java/com/reliancy/jabba/FileConfig.java new file mode 100644 index 0000000..a35acaf --- /dev/null +++ b/src/main/java/com/reliancy/jabba/FileConfig.java @@ -0,0 +1,54 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba; + +import java.util.HashMap; + +public class FileConfig implements Config{ + String path; + final HashMap props=new HashMap<>(); + public FileConfig(String p){ + path=p; + load(); + } + public FileConfig(){ + this(null); + } + public void clear(){ + props.clear(); + } + @Override + public void load() { + + } + @Override + public void save() { + } + + @Override + public String getId() { + return path; + } + + public void setId(String path) { + this.path = path; + } + @Override + public T getProperty(Config.Property key, T def) { + if(props.containsKey(key.getName())) return key.getTyp().cast(props.get(key.getName())); + else return def; + } + + @Override + public Config setProperty(Config.Property key, T val) { + props.put(key.getName(),val); + return this; + } + + +} diff --git a/src/main/java/com/reliancy/jabba/FileServer.java b/src/main/java/com/reliancy/jabba/FileServer.java index 3d4d852..b9fd69b 100644 --- a/src/main/java/com/reliancy/jabba/FileServer.java +++ b/src/main/java/com/reliancy/jabba/FileServer.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.jabba; import com.reliancy.util.Resources; import java.io.File; @@ -40,7 +47,7 @@ public class FileServer extends EndPoint implements Resources.PathRewrite{ @Override public void serve(Request request, Response response) throws IOException { String path=request.getPath(); - log().info("serving:"+path); + log().debug("to serve:"+path); for(String prefix:map.keySet()){ boolean match=path.startsWith(prefix); if(match){ @@ -49,13 +56,14 @@ public class FileServer extends EndPoint implements Resources.PathRewrite{ 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); + this.log().debug("\tfound:"+f); writeResource(f,response); return; } } response.setStatus(Response.HTTP_NOT_FOUND); response.getEncoder().writeln("missing file:{0}",path); + this.log().error("not found:"+path); } /** * we prefix our path for disk and class contexts. @@ -111,7 +119,7 @@ public class FileServer extends EndPoint implements Resources.PathRewrite{ public Iterator enumRoutes(){ return map.keySet().iterator(); } - public void exportRoutes(RouterEndPoint rep) { + public void exportRoutes(RoutedEndPoint rep) { streamRoutes().forEach(up->rep.addRoute("GET",up+".*",this)); } } diff --git a/src/main/java/com/reliancy/jabba/HTTP.java b/src/main/java/com/reliancy/jabba/HTTP.java index 4ec93c7..c2f8067 100644 --- a/src/main/java/com/reliancy/jabba/HTTP.java +++ b/src/main/java/com/reliancy/jabba/HTTP.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.jabba; import java.io.File; diff --git a/src/main/java/com/reliancy/jabba/JettyApp.java b/src/main/java/com/reliancy/jabba/JettyApp.java new file mode 100644 index 0000000..622c151 --- /dev/null +++ b/src/main/java/com/reliancy/jabba/JettyApp.java @@ -0,0 +1,330 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba; + +import java.io.IOException; +import java.util.EventListener; +import java.util.HashMap; +import java.util.Map; + +import com.reliancy.jabba.sec.NotAuthentic; +import com.reliancy.jabba.sec.Secured; +import com.reliancy.jabba.sec.SecurityActor; +import com.reliancy.jabba.sec.SecurityPolicy; +import com.reliancy.jabba.sec.plain.PlainSecurityStore; +import com.reliancy.jabba.ui.Feedback; +import com.reliancy.jabba.ui.FeedbackLine; +import com.reliancy.jabba.ui.Menu; +import com.reliancy.jabba.ui.MenuItem; +import com.reliancy.jabba.ui.Rendering; +import com.reliancy.jabba.ui.Template; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; + +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 JettyApp extends App implements Handler{ + enum State{ + STOPPED, + FAILED, + STARTING, + STARTED, + STOPPING, + RUNNING + } + protected Connector[] connectors; + protected Server jetty; + private volatile State _state; + + public JettyApp() { + super("JettyApp"); + jetty = new Server(); + jetty.setHandler(this); + _state=State.STOPPED; + } + /** implementation of jetty handler interface */ + @Override + public Server getServer() { + return jetty; + } + + @Override + public void setServer(Server arg0) { + jetty=arg0; + } + @Override + public boolean addEventListener(EventListener arg0) { + return false; + } + @Override + public boolean removeEventListener(EventListener arg0) { + return false; + } + protected void setState(State s){ + _state=s; + } + @Override + public boolean isFailed() { + return _state==State.FAILED; + } + + @Override + public boolean isRunning() { + return _state==State.RUNNING; + } + + @Override + public boolean isStarted() { + return _state==State.STARTED; + } + + @Override + public boolean isStarting() { + return _state==State.STARTING; + } + + @Override + public boolean isStopped() { + return _state==State.STOPPED; + } + + @Override + public boolean isStopping() { + return _state==State.STOPPING; + } + @Override + public void start() throws Exception { + _state=State.STARTED; + } + + @Override + public void stop() throws Exception { + _state=State.STOPPED; + } + + @Override + public void destroy() { + } + /** + * Our implementation of a handle process. + * In case of exception if we can locate /tempaltes/error.hbs we use it else we re-throw. + */ + @Override + public void handle(String target, + Request baseRequest, + HttpServletRequest request, + HttpServletResponse response) + throws IOException + { + 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); + process(req,resp); + }catch(IOException ioex){ + Template t=Template.find("/templates/error.hbs"); + if(t==null) throw ioex; + Rendering.begin(t) + .with(ioex) + .end(resp.getEncoder().getWriter()); + log().error("error:",ioex); + }catch(RuntimeException rex){ + Template t=Template.find("/templates/error.hbs"); + if(t==null) throw rex; + Rendering.begin(t) + .with(rex) + .end(resp.getEncoder().getWriter()); + log().error("error:",rex); + }finally{ + ss.end(); + } + } + /** our own interface specific to jetty engine*/ + + public Connector[] getConnectors(){ + if(connectors!=null) return connectors; + ServerConnector connector = new ServerConnector(jetty); + connector.setReuseAddress(false); + connector.setPort(8090); + connectors=new Connector[] {connector}; + return connectors; + } + public void begin(Config conf) throws Exception{ + super.begin(conf); + jetty.setConnectors(getConnectors()); + try{ + jetty.start(); + }catch(Exception ex){ + setState(State.FAILED); + if(ex.getCause() instanceof java.net.BindException){ + log().error("Bind issue",ex); + Thread.sleep(3000); + }else throw ex; + } + } + public void work() throws InterruptedException{ + setState(State.RUNNING); + if(jetty!=null) jetty.join(); + } + public void end() throws Exception{ + //setState(State.STOPPING); + super.end(); + Connector[] connectors=jetty.getConnectors(); + // System.out.println(connectors); + if(connectors!=null) for(Connector c:connectors){ + ServerConnector cc=(ServerConnector) c; + //System.out.println("stopping connecor:"+cc); + try{ + cc.stop(); + cc.getConnectedEndPoints().forEach((endpoint)-> { + //System.out.println("closing endpoint:"+endpoint); + endpoint.close(); + }); + }finally{ + cc.close(); + //System.out.println("closing connecor:"+cc.getState()); + } + } + //System.out.println("signaling..."); + jetty.stop(); + //setState(State.STOPPED); + //System.out.println("cleanup..."); + System.gc(); + //System.out.println("return..."); + } + public static void main( String[] args ) throws Exception{ + //System.out.println("Hello World!"); + Template.search_path("./var",App.class); + JettyApp app=new JettyApp(); + app.addAppSession(); + SecurityPolicy secpol=new SecurityPolicy().setStore(new PlainSecurityStore()); + app.setSecurityPolicy(secpol); + RoutedEndPoint rep=new RoutedEndPoint().importMethods(app); + app.setRouter(rep); + FileServer fs=new FileServer("/static","./var/public"); + fs.exportRoutes(app.getRouter()); + Menu top_menu=Menu.request(Menu.TOP); + top_menu.add(new MenuItem("home")).addSpacer().add(new MenuItem("login")); + top_menu.setTitle("Jabba"); + app.run(new FileConfig()); + //System.out.println("Goodbye World!"); + } + + @Routed() + public String hello(){ + Map context = new HashMap<>(); + context.put("name", "Jared"); + String ret=""; + try { + Template t=Template.find("/templates/login.hbs"); + System.out.println("Template:"+t); + ret = t.render(context).toString(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return ret; + //#return "Hello World"; + } + @Routed( + path="/helloPlain" + ) + public void hello2(com.reliancy.jabba.Request req,Response resp) throws IOException{ + resp.getEncoder().writeln("Hi There"); + } + @Routed( + path="/hello3/{idd:int}" + ) + public String hello3(int id){ + return "Hello3:"+id; + } + @Routed( + path="/" + ) + public String home(){ + StringBuilder buf=new StringBuilder(); + buf.append("

Sample pages:

"); + buf.append("
plain
"); + buf.append("
parametric
"); + buf.append("
templated
"); + buf.append("
secured http
"); + buf.append("
secured form
"); + return buf.toString(); + } + @Routed + @Secured + public String secured(){ + return "We are secured"; + } + @Routed + @Secured( + login_form = "/login" + ) + public String secured_form(){ + return "We are secured by form"; + } + @Routed + public void login(com.reliancy.jabba.Request req,Response resp){ + //return "login form here"; + if(req.getVerb().equals("POST")){ + // here we need to process login and redirect + try{ + System.out.println("Post login"); + String userid=(String)req.getParam("userid",null); + String pwd=(String)req.getParam("password",null); + AppSession ass=AppSession.getInstance(); + System.out.println("SS:"+ass); + System.out.println("P:"+userid+"/"+pwd); + SecurityPolicy secpol=ass.getApp().getSecurityPolicy(); + SecurityActor user=secpol.authenticate(userid, pwd); + if(user==null) throw new NotAuthentic("invalid credentials"); + resp.setStatus(Response.HTTP_FOUND_REDIRECT); + //String old_url=request.getPath(); + //old_url=URLEncoder.encode(old_url,StandardCharsets.UTF_8.toString()); + resp.setHeader("Location","/home"); + }catch(Exception ex){ + log().error("error:",ex); + Feedback.get().push(FeedbackLine.error(ex.getLocalizedMessage())); + } + } + //Map context = new HashMap<>(); + //context.put("app_title", "Jabba Login"); + //context.put("name", "Jared"); + //ArrayList events=new ArrayList<>(); + + //Feedback.get().push(FeedbackLine.error("Error")); + //Feedback.get().push(FeedbackLine.info("Error")); + //Feedback.get().push(FeedbackLine.warn("Error")); + //context.put("feedback",events); + try { + resp.setContentType("text/html"); + Rendering.begin("/templates/login.hbs") + //.with("feedback",events) + .end(resp.getEncoder().getWriter()); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + throw new RuntimeException(e); + } + + } + +} diff --git a/src/main/java/com/reliancy/jabba/MethodDecorator.java b/src/main/java/com/reliancy/jabba/MethodDecorator.java new file mode 100644 index 0000000..789fb11 --- /dev/null +++ b/src/main/java/com/reliancy/jabba/MethodDecorator.java @@ -0,0 +1,44 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; + +/** Decorator is a handler for method annotations. + * The name is borrowed from python because it connects java annotations to handler that are called before and after a method is invoked. + * Decorator itself is injected and called by methodendpoint as a filter but the factory does not have to return an object. + * it can just use the methodendpoint to register it somewhere once during startup. + */ +public abstract class MethodDecorator { + public static interface Factory{ + MethodDecorator assertDecorator(MethodEndPoint mep,Annotation ann); + } + static final ArrayList registry=new ArrayList<>(); + public static void publish(MethodDecorator.Factory d){ + if(!registry.contains(d)) registry.add(d); + } + public static void retract(MethodDecorator.Factory d){ + while (registry.remove(d)); + } + public static MethodDecorator query(MethodEndPoint mep,Annotation ann){ + for(MethodDecorator.Factory f:registry){ + MethodDecorator d=f.assertDecorator(mep, ann); + if(d!=null) return d; + } + return null; + } + MethodEndPoint method; + Annotation annotation; + public MethodDecorator(MethodEndPoint mep,Annotation ann){ + method=mep; + annotation=ann; + } + public abstract void beforeMethod(Request request, Response response); + public abstract void afterMethod(Request request, Response response); +} diff --git a/src/main/java/com/reliancy/jabba/MethodEndPoint.java b/src/main/java/com/reliancy/jabba/MethodEndPoint.java index 72a48b9..0c2a49c 100644 --- a/src/main/java/com/reliancy/jabba/MethodEndPoint.java +++ b/src/main/java/com/reliancy/jabba/MethodEndPoint.java @@ -1,7 +1,16 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.jabba; import java.io.IOException; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Parameter; +import java.util.ArrayList; import com.reliancy.util.Handy; @@ -12,16 +21,17 @@ public class MethodEndPoint extends EndPoint{ FULL, // one or more arguments need to do casting } - Route route; + Routed route; Object target; Method method; Parameter[] params; Class retType; InvokeProfile invokeType; - - public MethodEndPoint(Object target,Method m,Route r) { + ArrayList decorators=new ArrayList<>(); + + public MethodEndPoint(Object target,Method m) { super(target.getClass().getSimpleName()+"."+m.getName()); - this.route=r; + this.route=m.getAnnotation(Routed.class); this.target=target; this.method=m; this.params=m.getParameters(); @@ -33,8 +43,27 @@ public class MethodEndPoint extends EndPoint{ if(params.length==0){ invokeType=InvokeProfile.NOARG; } + bindDecorators(); + } + public String getVerb(){ + return route.verb(); + } + public String getPath() { + String ret=route.path(); + if(!ret.startsWith("/")) ret="/"+ret; + ret=ret.replace("{method}",method.getName()); + return ret; + } + public Routed getRoute(){ + return route; + } + /** pulls in and adds decorator filters to this methodcall that are supported. */ + protected final void bindDecorators(){ + for(Annotation a:method.getAnnotations()){ + MethodDecorator d=MethodDecorator.query(this,a); + if(d!=null) decorators.add(d); + } } - @Override public void serve(Request request, Response response) throws IOException{ log().info("Serving method....{}",invokeType); @@ -50,8 +79,7 @@ public class MethodEndPoint extends EndPoint{ encodeResponse(ret,response); break; } - default:{ - // here we do full unmarshalling, marshalling + default:{ // here we do full unmarshalling, marshalling Object[] argVals=decodeRequest(request); ret=method.invoke(target,argVals); encodeResponse(ret,response); @@ -62,12 +90,6 @@ public class MethodEndPoint extends EndPoint{ 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 params=new ArrayList(); + Pattern regex; + Object payload; + + public RouteDetector(String verb,String path){ + this.verb=verb; + this.path=path; + if(this.verb==null) this.verb="GET|POST|DELETE"; + verb=verb.toUpperCase(); + pattern=toPattern(verb, path); + regex=Pattern.compile(pattern); + 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 String toString(){ + return getPattern(); + } + public String getPattern(){ + return pattern; + } + public String getVerb(){ + return verb; + } + public String getPath(){ + return path; + } + public static String toPattern(String verb,String path){ + String pathPattern=path.replaceAll("\\{(.+)\\}","(.+)"); + String ret=Handy.wrap(verb,"(",")")+" "+pathPattern; + if(!ret.endsWith("/") && !ret.endsWith("$")) ret+="$"; + return ret; + } + public boolean hasParams(){ + return !params.isEmpty(); + } + public ArrayList getParams(){ + return params; + } + public boolean matches(String pat){ + return matches(pat,null); + } + public boolean matches(String pat,Map p){ + Matcher m=regex.matcher(pat); + if(m.find()){ // do we match + // we do - now possibly extract params + if(p!=null){ + ArrayList pms=getParams(); + for(int i=0;i routes=new HashMap<>(); - HashMap> routeParams=new HashMap<>(); - ArrayList patterns=new ArrayList<>(); // route patterns ordered +public class RoutedEndPoint extends EndPoint{ + HashMap routes=new HashMap<>(); // route pattern to endpoint + ArrayList detectors=new ArrayList<>(); // route patterns ordered int[] indexes; // indexes for each route within regex Pattern regex; - public RouterEndPoint() { - super(null); + public RoutedEndPoint() { + super("router"); } @Override @@ -32,7 +39,6 @@ public class RouterEndPoint extends EndPoint{ if(m!=null){ //HashMap 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){ @@ -54,40 +60,30 @@ public class RouterEndPoint extends EndPoint{ 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 params=new ArrayList(); - 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); + RouteDetector det=new RouteDetector(verb,path); + detectors.add(det); + routes.put(det.getPattern(),mm); } 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)+")"; + Collections.sort(detectors,Comparator.comparing((det)->{return -det.getPath().length();})); + String fullPat=detectors + .stream() + .map(RouteDetector::toString) + .collect(Collectors.joining(")|(")); + fullPat = "("+fullPat+")"; + //System.out.println("FUll:"+fullPat); regex=Pattern.compile(fullPat); // also recompute indexes - indexes=new int[patterns.size()]; + indexes=new int[detectors.size()]; int index=1; for (int i = 0; i < indexes.length; i++) { indexes[i]=index; - String p=patterns.get(i); + RouteDetector det=detectors.get(i); index+=2; // this includes the verb group - if(routeParams.containsKey(p)){ // this includes any param groups - index+=routeParams.get(p).size(); + if(det.hasParams()){ // this includes any param groups + index+=det.getParams().size(); } } //Arrays.stream(indexes).forEach(e->System.out.println(e+" ")); @@ -102,8 +98,8 @@ public class RouterEndPoint extends EndPoint{ /** * Find the route and return also url params. * url params are saved in two ways by name and by pos. - * @param m - * @param routeParams + * @param m matcher to check + * @param p parameters to reference * @return */ public String evalMatcher(Matcher m,Map p){ @@ -120,9 +116,10 @@ public class RouterEndPoint extends EndPoint{ 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 pms=routeParams.get(ret); + RouteDetector det=detectors.get(rindex); + //String ret=patterns.get(rindex); + if(p!=null && det.hasParams()){ + ArrayList pms=det.getParams(); for(int i=0;i routes=new LinkedList<>(); + Class type=target.getClass(); + while (type != null) { + for(Method m : type.getDeclaredMethods()){ + //System.out.println("Method:"+m.toString()); + if(m.getAnnotation(Routed.class)!=null){ + routes.add(0,m); + } + } + type = type.getSuperclass(); + } + for(Method m:routes){ + MethodEndPoint mm=new MethodEndPoint(target,m); + addRoute(mm.getVerb(),mm.getPath(),mm); + } + return this; } } diff --git a/src/main/java/com/reliancy/jabba/Router.java b/src/main/java/com/reliancy/jabba/Router.java deleted file mode 100644 index 19deb0f..0000000 --- a/src/main/java/com/reliancy/jabba/Router.java +++ /dev/null @@ -1,231 +0,0 @@ -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 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 context = new HashMap<>(); - context.put("name", "Jared"); - String ret=""; - try { - Template.search_path("./var",SecurityPolicy.class); - Template t=Template.find("/templates/login.hbs"); - 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; - } -} diff --git a/src/main/java/com/reliancy/jabba/Session.java b/src/main/java/com/reliancy/jabba/Session.java index b545ca7..dd7ba17 100644 --- a/src/main/java/com/reliancy/jabba/Session.java +++ b/src/main/java/com/reliancy/jabba/Session.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.jabba; /** Session is temporary storage. diff --git a/src/main/java/com/reliancy/jabba/sec/NeedCredentials.java b/src/main/java/com/reliancy/jabba/sec/NeedCredentials.java new file mode 100644 index 0000000..66da873 --- /dev/null +++ b/src/main/java/com/reliancy/jabba/sec/NeedCredentials.java @@ -0,0 +1,20 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.sec; + +import com.reliancy.util.CodeException; +import com.reliancy.util.ResultCode; + +public class NeedCredentials extends CodeException { + public static final int CODE=ResultCode.defineFailure(1,SecurityPolicy.class,"please provide credentials"); + + public NeedCredentials(){ + super(CODE); + } + +} diff --git a/src/main/java/com/reliancy/jabba/sec/NotAuthentic.java b/src/main/java/com/reliancy/jabba/sec/NotAuthentic.java new file mode 100644 index 0000000..a14f93f --- /dev/null +++ b/src/main/java/com/reliancy/jabba/sec/NotAuthentic.java @@ -0,0 +1,18 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.sec; + +public class NotAuthentic extends RuntimeException { + public NotAuthentic(String message){ + super(message); + } + public NotAuthentic(String message,Throwable cause){ + super(message,cause); + } + +} diff --git a/src/main/java/com/reliancy/jabba/sec/NotPermitted.java b/src/main/java/com/reliancy/jabba/sec/NotPermitted.java new file mode 100644 index 0000000..4ba2de9 --- /dev/null +++ b/src/main/java/com/reliancy/jabba/sec/NotPermitted.java @@ -0,0 +1,19 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.sec; +/** + * 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); + } +} diff --git a/src/main/java/com/reliancy/jabba/sec/Securable.java b/src/main/java/com/reliancy/jabba/sec/Securable.java new file mode 100644 index 0000000..36800fa --- /dev/null +++ b/src/main/java/com/reliancy/jabba/sec/Securable.java @@ -0,0 +1,37 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.sec; + +import java.util.List; + +import com.reliancy.util.Handy; + +/** + * Any entity that can be secured or permissions set for an actor. + */ +public interface Securable { + public SecurityStore getStore(); + public Integer getId(); + default public Integer getOwnerId(){ + return getOwner().getId(); + } + public String getKind(); + public String getName(); + default public String getTitle(){ + return Handy.prettyPrint(getName()); + } + public String getIcon(); + public Securable getOwner(); + public List getOwnedSecurables(); + public List getDirectPermits(); + public SecurityPermit getPermit(Securable sec); + public SecurityPolicy getPolicy(); + public default boolean isEssential(){ + return false; + }; +} diff --git a/src/main/java/com/reliancy/jabba/sec/Secured.java b/src/main/java/com/reliancy/jabba/sec/Secured.java new file mode 100644 index 0000000..f6b0c55 --- /dev/null +++ b/src/main/java/com/reliancy/jabba/sec/Secured.java @@ -0,0 +1,20 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.sec; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +/** Annotation to indicate that this resource requruies authenticated actor and/or permit. + * first of all we register this route with securitypolicy and enforce actor presence. + * if user is not logged in we send to login form or use one of our protocols. + * Additionally we can specify permits that are required. + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface Secured { + String login_form() default ""; + String permits() default ""; +} diff --git a/src/main/java/com/reliancy/jabba/sec/SecurityActor.java b/src/main/java/com/reliancy/jabba/sec/SecurityActor.java new file mode 100644 index 0000000..17e44aa --- /dev/null +++ b/src/main/java/com/reliancy/jabba/sec/SecurityActor.java @@ -0,0 +1,22 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.sec; + +/** + * Interface that is implemented by any User or Principal entity. + * Often an AppSession will be determined in many ways by the user. + * SecurityActor is a very special type of Securable. + */ +public interface SecurityActor extends Securable{ + SecurityActor authPassword(String password); + /** returns HA1 signature for Digest protocol. */ + String getDigestSignature(String realm); + default boolean isRole(){ + return false; + }; +} diff --git a/src/main/java/com/reliancy/jabbasec/SecurityPermit.java b/src/main/java/com/reliancy/jabba/sec/SecurityPermit.java similarity index 59% rename from src/main/java/com/reliancy/jabbasec/SecurityPermit.java rename to src/main/java/com/reliancy/jabba/sec/SecurityPermit.java index 0cd3210..be7f96e 100644 --- a/src/main/java/com/reliancy/jabbasec/SecurityPermit.java +++ b/src/main/java/com/reliancy/jabba/sec/SecurityPermit.java @@ -1,4 +1,11 @@ -package com.reliancy.jabbasec; +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.sec; /** * 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. @@ -6,6 +13,8 @@ package com.reliancy.jabbasec; * We should start implementing from security policy and maybe we do not even need this class. */ public interface SecurityPermit { + public SecurityStore getStore(); + public Integer getId(); public SecurityActor getActor(); public Securable getSubject(); public boolean canRead(); @@ -13,4 +22,5 @@ public interface SecurityPermit { public boolean canDelete(); public boolean canCreate(); public boolean canSecure(); + public boolean canExecute(); } diff --git a/src/main/java/com/reliancy/jabba/sec/SecurityPolicy.java b/src/main/java/com/reliancy/jabba/sec/SecurityPolicy.java new file mode 100644 index 0000000..03fc259 --- /dev/null +++ b/src/main/java/com/reliancy/jabba/sec/SecurityPolicy.java @@ -0,0 +1,224 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.sec; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +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.jabba.RouteDetector; +import com.reliancy.jabba.MethodDecorator; +import com.reliancy.jabba.MethodEndPoint; +import com.reliancy.util.CodeException; +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 implements MethodDecorator.Factory{ + public static String REALM="reliancy"; + public static final String KEY_NAME="jbauth"; + protected String secret="sdfklgj 7150 9178-54=09"; + protected ArrayList protocols; + protected SecurityActor admin; + protected SecurityActor guest; + protected final HashMap secured_pat=new HashMap<>(); // paths that require user + protected SecurityStore store; + + public SecurityPolicy() { + super(SecurityPolicy.class.getSimpleName().toLowerCase()); + protocols=new ArrayList<>(); + protocols.add(new SecurityProtocol.Digest()); + protocols.add(new SecurityProtocol.Basic()); + } + protected String getSecret(){ + return secret; + } + public SecurityPolicy setSecured(String path,Secured info){ + if(checkSecured(path)!=null) throw new IllegalStateException("Secured path cannot be secured again:"+path); + Pattern regex=Pattern.compile(path); + secured_pat.put(regex,info); + return this; + } + public Secured checkSecured(String path){ + for(Pattern p:secured_pat.keySet()){ + if(p.pattern().equals(path)) return secured_pat.get(p); + if(p.matcher(path).find()) return secured_pat.get(p); + } + return null; + } + @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 bad_cred){ + // we could not establish user + response.setStatus(Response.HTTP_FORBIDDEN); + response.getEncoder().writeObject(CodeException.getUserMessage(bad_cred)); + }catch(NeedCredentials no_cred){ + String login_form=no_cred.get("login_form"); + if(Handy.isBlank(login_form)){ + // we got no login form use HTTP auth + response.setStatus(Response.HTTP_UNAUTHORIZED); + String auth_supported=protocols.get(0).getSignature(REALM); + //String auth_supported=protocols.stream().map(SecurityProtocol::getName).collect(Collectors.joining(",")); + response.setHeader("WWW-Authenticate",auth_supported); + }else{ + // we got a login form do a redirect + response.setStatus(Response.HTTP_FOUND_REDIRECT); + String old_url=request.getPath(); + old_url=URLEncoder.encode(old_url,StandardCharsets.UTF_8.toString()); + //old_url=flask.escape(request.url.replace(request.url_root.strip("/"),"")) + //resp=flask.redirect("{}?next={}".format(login_pg,old_url),code=303) + response.setHeader("Location",login_form+"?next="+old_url); + } + } + } + @Override + public void after(Request request, Response response) throws IOException { + } + @Override + public void serve(Request request, Response response) throws IOException { + // nothing to do here + } + /** authenticates or establishes user based on user and password. + * same as loadActor but with first param being admin account. + * @param name userid + * @param pwd password + * @return user we could establish + * @throws NotPermitted + * @throws IOException + */ + public SecurityActor authenticate(String name, String pwd) throws NotPermitted, IOException{ + return loadActor(admin, name, pwd); + } + /** authenticates or establishes user based on request and updates response. + * this method might redirect to a login view. once it is done + * @param req + * @return user we could establish + * @throws NotPermitted + * @throws IOException + */ + public SecurityActor authenticate(Request req) throws IOException, NotAuthentic, NeedCredentials{ + // must recover user from cookies or by redirecting to login + String verb=req.getVerb(); + String path=req.getPath(); + log().info("Path:"+path); + String secpath=verb+" "+path; + Secured secinfo=checkSecured(secpath); + if(secinfo==null){ + return null; // this path is not secured + } + log().info("\tuser is needed, send back auth resp"); + String auth=req.getCookie(KEY_NAME,null); + //log().info("Auth1:"+auth); + if(auth!=null){ + // we have an auth cookie - encoded user login + Map 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"); + //log().info("Auth2:"+auth); + 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); + } + throw new NeedCredentials().put("login_form",secinfo.login_form()); + //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); + } + public SecurityPolicy setStore(SecurityStore store) throws NotPermitted, IOException{ + // this call will work unless store is locked already + guest=(SecurityActor)store.loadSecurable(null,SecurityStore.GUEST); + admin=(SecurityActor)store.loadSecurable(null,SecurityStore.ADMIN); + if(guest==null) throw new IllegalArgumentException("store is missing guest actor"); + if(admin==null) throw new IllegalArgumentException("store is missing admin actor"); + // now we lock store + store.setPolicy(this); + this.store=store; + return this; + } + public SecurityStore getStore(){ + return store; + } + /** will save a securable including a user if permitted via actor. */ + public void saveSecurable(SecurityActor actor, Securable sec) throws IOException{ + store.saveSecurable(actor, sec); + } + /** loads a securable by id given actor permits. */ + public Securable loadSecurable(SecurityActor actor, Integer id) throws IOException, NotPermitted{ + return store.loadSecurable(actor, id); + } + /** loads an actor given name and/or password. + * if actor is an admin and no password is given it looks up actor by name. + */ + public SecurityActor loadActor(SecurityActor actor, String name, String pwd) throws IOException, NotPermitted{ + return store.loadActor(actor, name, pwd); + } + /** + * we do not create actual decorators we just register this method so we can intercept routes. + */ + @Override + public MethodDecorator assertDecorator(MethodEndPoint mep, Annotation ann) { + if(!(ann instanceof Secured)) return null; + System.out.println("Assert decorator for:"+mep.getPath()); + String verb=mep.getVerb(); + String path=mep.getPath(); + String pat=RouteDetector.toPattern(verb, path); + setSecured(pat,(Secured)ann); + return null; + } + +} diff --git a/src/main/java/com/reliancy/jabba/sec/SecurityProtocol.java b/src/main/java/com/reliancy/jabba/sec/SecurityProtocol.java new file mode 100644 index 0000000..b5e2f30 --- /dev/null +++ b/src/main/java/com/reliancy/jabba/sec/SecurityProtocol.java @@ -0,0 +1,108 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.sec; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +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; + } + /// returns the signature to be send to client. + public String getSignature(String realm){ + return getName()+String.format(" realm=\"%s\"",realm); + } + + 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) throws NotPermitted, IOException{ + Map params=decode_params(tok); + String username=params.get("username"); + String realm=params.get("realm"); + String nonce=params.get("nonce"); + //String uri=params.get("uri"); + String rsp_in=params.get("response"); + SecurityActor usr=policy.loadActor(policy.admin,username,null); + //#print("USER:",usr) + if(usr!=null){ + String ha1=usr.getDigestSignature(realm); + String rsp_usr=digest_signature(ha1,nonce,req.getVerb(),req.getPath()); + //print("CHECK:",rsp_usr,rsp_in,uri,req.full_path) + if(!rsp_usr.equals(rsp_in)){ + //this user response does not match one provided + usr=null; + } + } + if(usr==null) throw new NotAuthentic("Invalid credentials"); + return usr; + } + public String digest_signature(String ha1,String nonce,String method,String uri){ + //ha1_msg=user+":"+realm+":"+password + //ha1=self.get_md5(ha1_msg) + String ha2_msg=method+":"+uri; + String ha2=Handy.hashMD5(ha2_msg); + String rsp_msg=ha1+":"+nonce+":"+ha2; + String rsp=Handy.hashMD5(rsp_msg); + return rsp; + } + /** return a dict of all the values sent.*/ + public Map decode_params(String tok){ + HashMap ret=new HashMap<>(); + for(String kv:tok.trim().split(",")){ + String[] args=kv.trim().split("=",2); + String k=Handy.trimRight(args[0]," \t\r\f\n"); + String v=args.length>1?Handy.trimEvenly(args[1],"'\""):""; + ret.put(k,v); + } + return ret; + } + /// returns a new nonce value. + public String new_nonce(){ + //String nonce=util.get_md5(str(id(self))) + String nonce=Handy.hashMD5(String.valueOf(hashCode())); + return nonce; + } + @Override + public String getSignature(String realm){ + //return getName()+String.format(" realm=\"%s\"",realm); + String nonce=new_nonce(); + String ret=super.getSignature(realm); + return String.format("%s, nonce=\"%s\"",ret,nonce); + } + + } +} diff --git a/src/main/java/com/reliancy/jabba/sec/SecurityStore.java b/src/main/java/com/reliancy/jabba/sec/SecurityStore.java new file mode 100644 index 0000000..bd8065d --- /dev/null +++ b/src/main/java/com/reliancy/jabba/sec/SecurityStore.java @@ -0,0 +1,41 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.sec; + +import java.io.IOException; +import java.util.List; + +/** + * Storage interface for security elemenets such as securables, actors and permits. + * Storage is unlocked until a policy is set then it gets locked and it matters who + * admin actor is. + * + * The lock is establish witht the install of a policy. The outside code including policy can + * query any securable and especially admin account. Once the policy is set then admin account + * must be used for privileged access. + */ +public interface SecurityStore { + public static int GUEST=-1; + public static int ADMIN=-2; + + public Securable newSecurable(); + public SecurityActor newActor(); + public SecurityPermit newPermit(); + public void deleteSecurable(SecurityActor actor, Securable sec) throws IOException; + public void saveSecurable(SecurityActor actor, Securable sec) throws IOException; + public Securable loadSecurable(SecurityActor actor, Integer id) throws IOException, NotPermitted; + public SecurityActor loadActor(SecurityActor actor, String name, String pwd) throws IOException, NotPermitted; + public List loadSecurables(SecurityActor actor, Securable sec) throws IOException, NotPermitted; + public void deletePermit(SecurityActor actor, SecurityPermit permit) throws IOException; + public void savePermit(SecurityActor actor, SecurityPermit permit) throws IOException; + public SecurityPermit loadPermit(SecurityActor actor, Integer id) throws IOException, NotPermitted; + public List loadPermitsBy(SecurityActor actor, SecurityActor sec) throws IOException, NotPermitted; + public List loadPermitsOn(SecurityActor actor, Securable sec) throws IOException, NotPermitted; + public void setPolicy(SecurityPolicy policy); + public SecurityPolicy getPolicy(); +} diff --git a/src/main/java/com/reliancy/jabba/sec/plain/PlainActor.java b/src/main/java/com/reliancy/jabba/sec/plain/PlainActor.java new file mode 100644 index 0000000..52dc941 --- /dev/null +++ b/src/main/java/com/reliancy/jabba/sec/plain/PlainActor.java @@ -0,0 +1,46 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.sec.plain; + +import com.reliancy.jabba.sec.SecurityActor; +import com.reliancy.util.Handy; + +public class PlainActor extends PlainSecurable implements SecurityActor{ + String password; + boolean role; + + @Override + public SecurityActor authPassword(String pwd) { + if(password.equals(pwd)) return this; + return null; + } + + protected String getPassword() { + return password; + } + + protected PlainActor setPassword(String password) { + this.password = password; + return this; + } + + public String getDigestSignature(String realm){ + String ha1_msg=this.getName()+":"+realm+":"+password; + String ha1=Handy.hashMD5(ha1_msg); + return ha1; + } + + public boolean isRole() { + return role; + } + + public void setRole(boolean role) { + this.role = role; + } + +} diff --git a/src/main/java/com/reliancy/jabba/sec/plain/PlainPermit.java b/src/main/java/com/reliancy/jabba/sec/plain/PlainPermit.java new file mode 100644 index 0000000..a004686 --- /dev/null +++ b/src/main/java/com/reliancy/jabba/sec/plain/PlainPermit.java @@ -0,0 +1,76 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.sec.plain; + +import com.reliancy.jabba.sec.Securable; +import com.reliancy.jabba.sec.SecurityActor; +import com.reliancy.jabba.sec.SecurityPermit; +import com.reliancy.jabba.sec.SecurityStore; + +public class PlainPermit implements SecurityPermit{ + SecurityStore store; + Integer id; + SecurityActor actor; + Securable subject; + boolean can_read; + boolean can_write; + boolean can_delete; + boolean can_create; + boolean can_secure; + boolean can_execute; + + @Override + public SecurityStore getStore(){ + return store; + } + + @Override + public Integer getId() { + return id; + } + + @Override + public SecurityActor getActor() { + return actor; + } + + @Override + public Securable getSubject() { + return subject; + } + + @Override + public boolean canRead() { + return can_read; + } + + @Override + public boolean canWrite() { + return can_write; + } + + @Override + public boolean canDelete() { + return can_delete; + } + + @Override + public boolean canCreate() { + return can_create; + } + + @Override + public boolean canSecure() { + return can_secure; + } + @Override + public boolean canExecute() { + return can_execute; + } + +} diff --git a/src/main/java/com/reliancy/jabba/sec/plain/PlainSecurable.java b/src/main/java/com/reliancy/jabba/sec/plain/PlainSecurable.java new file mode 100644 index 0000000..3ddee63 --- /dev/null +++ b/src/main/java/com/reliancy/jabba/sec/plain/PlainSecurable.java @@ -0,0 +1,109 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.sec.plain; + +import java.util.List; + +import com.reliancy.jabba.sec.Securable; +import com.reliancy.jabba.sec.SecurityPermit; +import com.reliancy.jabba.sec.SecurityPolicy; +import com.reliancy.jabba.sec.SecurityStore; +import com.reliancy.util.Handy; + +public class PlainSecurable implements Securable{ + SecurityStore store; + Integer id; + Securable owner; + String kind; + String name; + String title; + String icon; + boolean essential; + + @Override + public SecurityStore getStore() { + return store; + } + @Override + public Integer getId() { + return id; + } + protected PlainSecurable setId(Integer id){ + this.id=id; + return this; + } + @Override + public String getKind() { + return kind; + } + public PlainSecurable setKind(String v){ + kind=v; + return this; + } + @Override + public String getName() { + return name; + } + public PlainSecurable setName(String v){ + name=v; + return this; + } + + @Override + public String getTitle() { + return title!=null?title:Handy.prettyPrint(getName()); + } + public PlainSecurable setTitle(String v){ + title=v; + return this; + } + + @Override + public String getIcon() { + return icon; + } + public PlainSecurable setIcon(String v){ + icon=v; + return this; + } + + @Override + public Securable getOwner() { + return owner; + } + + @Override + public List getOwnedSecurables() { + return null; + } + + @Override + public List getDirectPermits() { + // TODO Auto-generated method stub + return null; + } + + @Override + public SecurityPermit getPermit(Securable sec) { + // TODO Auto-generated method stub + return null; + } + + @Override + public SecurityPolicy getPolicy() { + return store!=null?store.getPolicy():null; + } + public boolean isEssential() { + return essential; + } + public PlainSecurable setEssential(boolean essential) { + this.essential = essential; + return this; + } + +} diff --git a/src/main/java/com/reliancy/jabba/sec/plain/PlainSecurityStore.java b/src/main/java/com/reliancy/jabba/sec/plain/PlainSecurityStore.java new file mode 100644 index 0000000..5c4076c --- /dev/null +++ b/src/main/java/com/reliancy/jabba/sec/plain/PlainSecurityStore.java @@ -0,0 +1,198 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.sec.plain; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.reliancy.dbo.Bag; +import com.reliancy.jabba.Path; +import com.reliancy.jabba.sec.NotPermitted; +import com.reliancy.jabba.sec.Securable; +import com.reliancy.jabba.sec.SecurityActor; +import com.reliancy.jabba.sec.SecurityPermit; +import com.reliancy.jabba.sec.SecurityPolicy; +import com.reliancy.jabba.sec.SecurityStore; + + +/** + * PlainSecurityStore is a container for plain security elements. + * It will implement a simple in-memory list of items. Optionally it will + * have be able to load or save its state to disk as json. + * + * + */ +public class PlainSecurityStore implements SecurityStore { + final Bag securables=new Bag(); + final Bag permits=new Bag(); + SecurityPolicy policy; + PlainActor guest; + PlainActor admin; + Path path; + + public PlainSecurityStore() { + guest=(PlainActor) newActor(); + guest.setPassword("").setName("guest").setEssential(true); + admin=(PlainActor) newActor(); + admin.setPassword("admin").setName("admin").setTitle("Administrator").setEssential(true); + securables.add(guest); + securables.add(admin); + } + public PlainSecurityStore setPath(Path p){ + path=p; + return this; + } + public void load() throws IOException{ + + } + public void save() throws IOException{ + + } + @Override + public Securable newSecurable() { + return new PlainSecurable(); + } + + @Override + public SecurityActor newActor() { + return new PlainActor(); + } + + @Override + public SecurityPermit newPermit() { + return new PlainPermit(); + } + + @Override + public void deleteSecurable(SecurityActor actor, Securable sec) throws IOException { + if(policy!=null){ + // if policy is set only admin level users can access actors via securable + if(actor!=admin && actor!=sec.getOwner()) throw new NotPermitted("admin or owner rights required"); + } + securables.remove((PlainSecurable)sec); + } + + @Override + public void saveSecurable(SecurityActor actor, Securable sec) throws IOException { + if(policy!=null){ + // if policy is set only admin level users can access actors via securable + if(actor!=admin && actor!=sec.getOwner()) throw new NotPermitted("admin or owner rights required"); + } + securables.add((PlainSecurable)sec); + } + + @Override + public Securable loadSecurable(SecurityActor actor, Integer id) throws IOException, NotPermitted { + if(policy!=null){ + // if policy is set only admin level users can access actors via securable + if(actor!=admin) throw new NotPermitted("admin rights required"); + } + if(id==ADMIN) return admin; + if(id==GUEST) return guest; + for(Securable sec:securables){ + if(sec.getId()==id) return sec; + } + return null; + } + + @Override + public SecurityActor loadActor(SecurityActor actor, String name, String pwd) throws IOException, NotPermitted { + if(policy!=null){ + // if policy is set only admin level users can access actors via securable + if(actor!=admin) throw new NotPermitted("admin rights required"); + } + for(Securable sec:securables){ + if(!(sec instanceof SecurityActor)) continue; // skip over non actors + SecurityActor a=(SecurityActor) sec; + if(!a.getName().equalsIgnoreCase(name)) continue; // name mismatch + if(pwd!=null && a.authPassword(pwd)==a) return a; // match on password if provided + boolean actor_permitted=actor==admin; + if(!actor_permitted) continue; // actor is not permitted + return a; // if permitted lookup by name role or user + } + return null; + } + + @Override + public List loadSecurables(SecurityActor actor, Securable sec) throws IOException, NotPermitted { + if(policy!=null){ + // if policy is set only admin level users can access actors via securable + if(actor!=admin && actor!=sec.getOwner()) throw new NotPermitted("admin or owner rights required"); + } + ArrayList ret=new ArrayList<>(); + for(Securable s:securables){ + if(sec==s.getOwner()) ret.add(s); + } + return ret; + } + + @Override + public void deletePermit(SecurityActor actor, SecurityPermit permit) throws IOException { + if(policy!=null){ + // if policy is set only admin level users can access actors via securable + if(actor!=admin) throw new NotPermitted("admin or owner rights required"); + } + permits.remove((PlainPermit)permit); + } + + @Override + public void savePermit(SecurityActor actor, SecurityPermit permit) throws IOException { + if(policy!=null){ + // if policy is set only admin level users can access actors via securable + if(actor!=admin) throw new NotPermitted("admin or owner rights required"); + } + permits.add((PlainPermit)permit); + } + + @Override + public SecurityPermit loadPermit(SecurityActor actor, Integer id) throws IOException, NotPermitted { + if(policy!=null){ + // if policy is set only admin level users can access actors via securable + if(actor!=admin) throw new NotPermitted("admin rights required"); + } + for(SecurityPermit p:permits){ + if(p.getId()==id) return p; + } + return null; + } + @Override + public List loadPermitsBy(SecurityActor actor, SecurityActor sec) throws IOException, NotPermitted { + if(policy!=null){ + // if policy is set only admin level users can access actors via securable + if(actor!=admin) throw new NotPermitted("admin rights required"); + } + ArrayList ret=new ArrayList<>(); + for(SecurityPermit p:permits){ + if(p.getActor()==sec) ret.add(p); + } + return ret; + } + @Override + public List loadPermitsOn(SecurityActor actor, Securable sec) throws IOException, NotPermitted { + if(policy!=null){ + // if policy is set only admin level users can access actors via securable + if(actor!=admin) throw new NotPermitted("admin rights required"); + } + ArrayList ret=new ArrayList<>(); + for(SecurityPermit p:permits){ + if(p.getSubject()==sec) ret.add(p); + } + return ret; + } + + @Override + public void setPolicy(SecurityPolicy policy) { + if(this.policy!=null) throw new IllegalStateException("Store is locked already."); + this.policy=policy; + } + @Override + public SecurityPolicy getPolicy() { + return this.policy; + } +} diff --git a/src/main/java/com/reliancy/jabba/ui/Feedback.java b/src/main/java/com/reliancy/jabba/ui/Feedback.java new file mode 100644 index 0000000..9e6abf8 --- /dev/null +++ b/src/main/java/com/reliancy/jabba/ui/Feedback.java @@ -0,0 +1,46 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ + +package com.reliancy.jabba.ui; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.ListIterator; + +import com.reliancy.jabba.AppSession; + +/** + * List of Feedback events with siphon like iterator. + * When iterating over the list it pops values as well. + */ +public class Feedback extends LinkedList{ + public static Feedback get(){ + AppSession ass=AppSession.getInstance(); + return ass!=null?ass.getFeedback():null; + } + class Siphon implements Iterator{ + final ListIterator backend; + public Siphon(ListIterator it){ + backend=it; + } + @Override + public boolean hasNext() { + return backend.hasNext(); + } + @Override + public FeedbackLine next() { + FeedbackLine ret=backend.next(); + backend.remove(); + return ret; + } + } + @Override + public Iterator iterator(){ + return new Siphon(this.listIterator()); + } + +} diff --git a/src/main/java/com/reliancy/jabba/ui/FeedbackLine.java b/src/main/java/com/reliancy/jabba/ui/FeedbackLine.java new file mode 100644 index 0000000..91e4aa6 --- /dev/null +++ b/src/main/java/com/reliancy/jabba/ui/FeedbackLine.java @@ -0,0 +1,44 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.ui; +/** + * Message object to inform users about things. + * + */ +public class FeedbackLine { + public static final String ERROR="danger"; + public static final String WARN="warning"; + public static final String INFO="info"; + String type; + String message; + public FeedbackLine(String typ,String message){ + this.type=typ; + this.message=message; + } + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } + public String getMessage() { + return message; + } + public void setMessage(String message) { + this.message = message; + } + public static FeedbackLine error(String message){ + return new FeedbackLine(ERROR, message); + } + public static FeedbackLine warn(String message){ + return new FeedbackLine(WARN, message); + } + public static FeedbackLine info(String message){ + return new FeedbackLine(INFO, message); + } +} diff --git a/src/main/java/com/reliancy/jabba/ui/Menu.java b/src/main/java/com/reliancy/jabba/ui/Menu.java new file mode 100644 index 0000000..419d429 --- /dev/null +++ b/src/main/java/com/reliancy/jabba/ui/Menu.java @@ -0,0 +1,76 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.ui; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** Helper or model class to manage menus and toolbars. + * + */ +public class Menu extends MenuItem{ + public static final String TOP="/menu/top"; + public static final String LEFT="/menu/left"; + + public static final HashMap menus=new HashMap<>(); + public static void publish(String name,Menu m){ + menus.put(name,m); + } + public static Menu request(String name){ + Menu ret=menus.get(name); + if(ret==null){ + ret=new Menu(name); + publish(name,ret); + } + return ret; + } + final ArrayList items=new ArrayList<>(); + public Menu(String id) { + super(id); + } + public int getSize(){ + return items.size(); + } + public Menu add(MenuItem itm){ + items.add(itm); + return this; + } + public Menu addSpacer(){ + return add(new MenuItem("###")); + } + public MenuItem find(String id){ + if(id.equalsIgnoreCase(getId())) return this; + for(MenuItem itm:items) if(id.equalsIgnoreCase(itm.getId())) return itm; + return null; + } + public List getItems() { + return items; + } + public Iterable getBefore() { + final ArrayList ret=new ArrayList<>(); + for(MenuItem itm:items){ + if("###".equals(itm.getId())) break; + ret.add(itm); + } + return ret; + } + public Iterable getAfter() { + final ArrayList ret=new ArrayList<>(); + boolean adding=false; + for(MenuItem itm:items){ + if("###".equals(itm.getId())){ + adding=true; + }else if(adding){ + ret.add(itm); + } + } + return ret; + } + +} diff --git a/src/main/java/com/reliancy/jabba/ui/MenuItem.java b/src/main/java/com/reliancy/jabba/ui/MenuItem.java new file mode 100644 index 0000000..41bc64d --- /dev/null +++ b/src/main/java/com/reliancy/jabba/ui/MenuItem.java @@ -0,0 +1,53 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.ui; + +import com.reliancy.util.Handy; + +/** Individual menu items within a menu. + * + */ +public class MenuItem { + final String id; + String title; + String url; + String icon; + public MenuItem(String id) { + this.id=id; + this.title=Handy.prettyPrint(id); + this.url="/"+id; + } + public String getId() { + return id; + } + public int getSize(){ + return 0; + } + public String getTitle() { + return title; + } + public MenuItem setTitle(String title) { + this.title = title; + return this; + } + public String getUrl() { + return url; + } + public MenuItem setUrl(String url) { + this.url = url; + return this; + } + public String getIcon() { + return icon; + } + public MenuItem setIcon(String icon) { + this.icon = icon; + return this; + } + +} diff --git a/src/main/java/com/reliancy/jabba/ui/Rendering.java b/src/main/java/com/reliancy/jabba/ui/Rendering.java new file mode 100644 index 0000000..d2f44f5 --- /dev/null +++ b/src/main/java/com/reliancy/jabba/ui/Rendering.java @@ -0,0 +1,77 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.ui; + +import java.io.IOException; +import java.io.Writer; +import java.util.HashMap; +import java.util.Map; + +import com.github.jknack.handlebars.Context; +import com.reliancy.util.CodeException; + +/** + * Data context and render task for our handlebars UI system. + * We create a rendering for a template, load it with data and then flush it at end into a document. + * In this class we also inject some global variables such as menu, feedback and user. + */ +public class Rendering extends HashMap{ + public static Rendering begin(Template t){ + Rendering ret=new Rendering(t); + ret.with("menu",Menu.request(Menu.TOP)); + ret.with("toolbar",Menu.request(Menu.LEFT)); + ret.with("feedback",Feedback.get()); + return ret; + } + public static Rendering begin(String path,Object ...sp){ + //if(sp==null ||sp.length==0) sp=new Object[]{"./var",App.class}; + Template t=Template.find(path,sp); + if(t==null) throw new CodeException(Template.ERR_BADTEMPLATE).put("template",path); + return begin(t); + } + Context ctx; + Template template; + public Rendering(Template t){ + ctx=Context.newBuilder(this).build(); + template=t; + } + public CharSequence end() throws IOException{ + try{ + CharSequence ret=template.render(ctx); + return ret; + }finally{ + ctx.destroy(); + } + } + public void end(Writer _out) throws IOException{ + try{ + template.render(ctx,_out); + }finally{ + ctx.destroy(); + } + } + public Rendering with(String key,Object val){ + put(key,val); + return this; + } + public Rendering with(Map kv){ + for(Map.Entry e:kv.entrySet()){ + put(e.getKey(),e.getValue()); + } + return this; + } + public Rendering with(Throwable ex){ + StringBuilder msg=new StringBuilder(); + StringBuilder title=new StringBuilder(); + CodeException.fillUserMessage(ex, msg, title); + with("error_title",title.toString()); + with("error_message",msg); + return this; + } + +} diff --git a/src/main/java/com/reliancy/util/Template.java b/src/main/java/com/reliancy/jabba/ui/Template.java similarity index 72% rename from src/main/java/com/reliancy/util/Template.java rename to src/main/java/com/reliancy/jabba/ui/Template.java index 57ed785..5b73dd6 100644 --- a/src/main/java/com/reliancy/util/Template.java +++ b/src/main/java/com/reliancy/jabba/ui/Template.java @@ -1,16 +1,27 @@ -package com.reliancy.util; +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.jabba.ui; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.Writer; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; import com.github.jknack.handlebars.Handlebars; +import com.github.jknack.handlebars.helper.StringHelpers; import com.github.jknack.handlebars.io.AbstractTemplateLoader; import com.github.jknack.handlebars.io.TemplateSource; import com.github.jknack.handlebars.io.URLTemplateSource; +import com.reliancy.util.Resources; +import com.reliancy.util.ResultCode; /* import com.hubspot.jinjava.Jinjava; @@ -49,17 +60,24 @@ public class Template { public TemplateSource sourceAt(String location) throws IOException { String fullpath=this.resolve(location); URL loc=Resources.findFirst(null,fullpath,Template.search_path); - System.out.println(location+":"+loc+":"+fullpath); + //System.out.println(location+":"+loc+":"+fullpath); if (loc == null) { Logger.getLogger(Template.class.getSimpleName()).warning("Missing template"+fullpath); throw new FileNotFoundException(location); } return new URLTemplateSource(location,loc); } - - } - static Handlebars handlebars = new Handlebars(new HBLoader()); + static Handlebars handlebars; + static{ + handlebars= new Handlebars(new HBLoader()); + StringHelpers.register(handlebars); + /* + for(ConditionalHelpers h:ConditionalHelpers.values()){ + + } + */ + } static Object[] search_path; static HashMap cache=new HashMap<>(); @@ -94,6 +112,7 @@ public class Template { if(sp!=null && sp.length>0) search_path=sp; return search_path; } + public static final int ERR_BADTEMPLATE=ResultCode.defineFailure(0x01,Template.class,"bad template: ${template}"); com.github.jknack.handlebars.Template recipe; final URL location; String source; @@ -114,13 +133,20 @@ public class Template { if(source==null) this.source=Resources.toString(location); return this; } - public CharSequence render(Map context) throws IOException{ + public CharSequence render(Object context) throws IOException{ if(source==null) load(); //String ret = jinjava.render(source, context); if(recipe==null){ recipe=handlebars.compileInline(source); } - String ret=recipe.apply(context); - return ret; + return recipe.apply(context); + } + public void render(Object context,Writer _out) throws IOException{ + if(source==null) load(); + //String ret = jinjava.render(source, context); + if(recipe==null){ + recipe=handlebars.compileInline(source); + } + recipe.apply(context,_out); } } diff --git a/src/main/java/com/reliancy/jabbasec/NotAuthentic.java b/src/main/java/com/reliancy/jabbasec/NotAuthentic.java deleted file mode 100644 index 87b6fc1..0000000 --- a/src/main/java/com/reliancy/jabbasec/NotAuthentic.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.reliancy.jabbasec; - -public class NotAuthentic extends RuntimeException { - public NotAuthentic(String message){ - super(message); - } - public NotAuthentic(String message,Throwable cause){ - super(message,cause); - } - -} diff --git a/src/main/java/com/reliancy/jabbasec/NotPermitted.java b/src/main/java/com/reliancy/jabbasec/NotPermitted.java deleted file mode 100644 index 24eda2a..0000000 --- a/src/main/java/com/reliancy/jabbasec/NotPermitted.java +++ /dev/null @@ -1,12 +0,0 @@ -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); - } -} diff --git a/src/main/java/com/reliancy/jabbasec/Securable.java b/src/main/java/com/reliancy/jabbasec/Securable.java deleted file mode 100644 index 7fb046a..0000000 --- a/src/main/java/com/reliancy/jabbasec/Securable.java +++ /dev/null @@ -1,20 +0,0 @@ -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 getOwnedSecurables(); - public List getDirectPermits(); - public SecurityPermit getPermit(Securable sec); - public SecurityPolicy getPolicy(); -} diff --git a/src/main/java/com/reliancy/jabbasec/SecurityActor.java b/src/main/java/com/reliancy/jabbasec/SecurityActor.java deleted file mode 100644 index 43f3fbd..0000000 --- a/src/main/java/com/reliancy/jabbasec/SecurityActor.java +++ /dev/null @@ -1,9 +0,0 @@ -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{ - -} diff --git a/src/main/java/com/reliancy/jabbasec/SecurityPolicy.java b/src/main/java/com/reliancy/jabbasec/SecurityPolicy.java deleted file mode 100644 index d3ef5ae..0000000 --- a/src/main/java/com/reliancy/jabbasec/SecurityPolicy.java +++ /dev/null @@ -1,125 +0,0 @@ -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 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 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{ - - } - -} diff --git a/src/main/java/com/reliancy/jabbasec/SecurityProtocol.java b/src/main/java/com/reliancy/jabbasec/SecurityProtocol.java deleted file mode 100644 index 1b57be7..0000000 --- a/src/main/java/com/reliancy/jabbasec/SecurityProtocol.java +++ /dev/null @@ -1,42 +0,0 @@ -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; - } - } -} diff --git a/src/main/java/com/reliancy/rec/DecoderSink.java b/src/main/java/com/reliancy/rec/DecoderSink.java index 136356a..7f9a633 100644 --- a/src/main/java/com/reliancy/rec/DecoderSink.java +++ b/src/main/java/com/reliancy/rec/DecoderSink.java @@ -1,3 +1,11 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ + package com.reliancy.rec; /** Similar to a SAX interface used by parsers for XML and JSON to assemble DOM structures. diff --git a/src/main/java/com/reliancy/rec/Hdr.java b/src/main/java/com/reliancy/rec/Hdr.java index b97e8c3..cfa16c9 100644 --- a/src/main/java/com/reliancy/rec/Hdr.java +++ b/src/main/java/com/reliancy/rec/Hdr.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.rec; import java.util.ArrayList; diff --git a/src/main/java/com/reliancy/rec/JSON.java b/src/main/java/com/reliancy/rec/JSON.java index b36c85f..c790181 100644 --- a/src/main/java/com/reliancy/rec/JSON.java +++ b/src/main/java/com/reliancy/rec/JSON.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.rec; import java.io.IOException; diff --git a/src/main/java/com/reliancy/rec/JSONDecoder.java b/src/main/java/com/reliancy/rec/JSONDecoder.java index 002d1f1..a4b8533 100644 --- a/src/main/java/com/reliancy/rec/JSONDecoder.java +++ b/src/main/java/com/reliancy/rec/JSONDecoder.java @@ -1,8 +1,10 @@ -/* - * 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. - */ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.rec; @@ -12,7 +14,7 @@ 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. - * + * TODO: reuse headers in an array if same structure * @author amer */ public class JSONDecoder implements TextDecoder,DecoderSink { diff --git a/src/main/java/com/reliancy/rec/JSONEncoder.java b/src/main/java/com/reliancy/rec/JSONEncoder.java index 50c990a..c918f1e 100644 --- a/src/main/java/com/reliancy/rec/JSONEncoder.java +++ b/src/main/java/com/reliancy/rec/JSONEncoder.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.rec; import java.io.IOException; diff --git a/src/main/java/com/reliancy/rec/Obj.java b/src/main/java/com/reliancy/rec/Obj.java index bf446c7..bdf6f1e 100644 --- a/src/main/java/com/reliancy/rec/Obj.java +++ b/src/main/java/com/reliancy/rec/Obj.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.rec; import java.util.ArrayList; diff --git a/src/main/java/com/reliancy/rec/Rec.java b/src/main/java/com/reliancy/rec/Rec.java index 976babb..dfa2470 100644 --- a/src/main/java/com/reliancy/rec/Rec.java +++ b/src/main/java/com/reliancy/rec/Rec.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.rec; /** diff --git a/src/main/java/com/reliancy/rec/Slot.java b/src/main/java/com/reliancy/rec/Slot.java index 1962a97..96c55a5 100644 --- a/src/main/java/com/reliancy/rec/Slot.java +++ b/src/main/java/com/reliancy/rec/Slot.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.rec; /** diff --git a/src/main/java/com/reliancy/rec/TextDecoder.java b/src/main/java/com/reliancy/rec/TextDecoder.java index e1087c2..4c11213 100644 --- a/src/main/java/com/reliancy/rec/TextDecoder.java +++ b/src/main/java/com/reliancy/rec/TextDecoder.java @@ -1,8 +1,10 @@ -/* - * 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. - */ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.rec; diff --git a/src/main/java/com/reliancy/rec/Vec.java b/src/main/java/com/reliancy/rec/Vec.java index 06b2b48..1167104 100644 --- a/src/main/java/com/reliancy/rec/Vec.java +++ b/src/main/java/com/reliancy/rec/Vec.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.rec; /** * dimensioned container of values. diff --git a/src/main/java/com/reliancy/util/CodeException.java b/src/main/java/com/reliancy/util/CodeException.java new file mode 100644 index 0000000..9b2a257 --- /dev/null +++ b/src/main/java/com/reliancy/util/CodeException.java @@ -0,0 +1,138 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ + +package com.reliancy.util; +import java.util.HashMap; + +/** One exception to rule them all. + * This exception works with ResultCode and represents and instance with context information. + * If a ResultCode is deemed parametric then we use provided parameters to update it when generating a message. + * + * @author amer + */ +public class CodeException extends RuntimeException { + + protected final int code; + protected final HashMap context=new HashMap<>(); + public CodeException(int code) { + this.code = code; + } + public CodeException(Throwable cause, int code) { + super(cause); + this.code = code; + } + @Override + public String toString(){ + return getMessage(); + } + public int getCode() { + return code; + } + public ResultCode getResultCode(){ + return ResultCode.get(code); + } + @Override + public String getMessage() { + ResultCode rcode=getResultCode(); + if(rcode!=null){ + boolean wrapped=(rcode.getCode()==ResultCode.FAILURE); + String msg=rcode.getMessage(); + if(msg.contains("$")){ + for(String key:context.keySet()){ + Object obj=context.get(key); + if(obj==null) continue; + String val=String.valueOf(obj); + msg=msg.replaceAll("\\$\\{"+key+"\\}",val); + } + }else if(this.getCause()!=null && wrapped){ + msg=CodeException.getUserMessage(this.getCause()); + } + return msg; + }else{ + return "("+String.format("%08X", code)+")"; + } + } + + @SuppressWarnings("unchecked") + public T get(String name) { + return (T)context.get(name); + } + + public CodeException put(String name, String value) { + context.put(name, value); + return this; + } + public static CodeException wrap(Throwable exception) { + if (exception instanceof CodeException) { + CodeException se = (CodeException)exception; + return se; + } else { + return new CodeException(exception,ResultCode.FAILURE); + } + } + public static String getUserMessage(Throwable ex,Object context) { + return getUserMessage(ex); + } + public static String getUserMessage(Throwable ex) { + StringBuilder buf=new StringBuilder(); + fillUserMessage(ex,buf,null); + return buf.toString(); + } + public static Throwable fillUserMessage(Throwable ex,StringBuilder msg,StringBuilder title) { + Throwable c = ex; + System.out.println(">>>"+c+"/"+c.getCause()); + while(c.getCause()!=null){ + Throwable cc= c.getCause(); + if(c.getMessage()==null){ + c=cc;continue; + } + String cMsg=c.getMessage(); + String ccMsg=cc.getMessage(); + System.out.println("!!!"+cMsg+"/"+c.getClass().getName()+"/"+cc.getClass().getName()); + boolean wrapped=(c instanceof CodeException) && ((CodeException)c).getCode()==ResultCode.FAILURE; + boolean plain_at=cMsg.equals(c.getClass().getName()); + boolean plain_sub=cMsg.equals(cc.getClass().getName()); + boolean same_msg=cMsg.equalsIgnoreCase(ccMsg); + System.out.println("\t"+plain_sub+"#"+cc+"$"+cc.getCause()+"*"+cc.getMessage()); + if(plain_at || plain_sub || cMsg.startsWith(cc.getClass().getName()+":") || same_msg || wrapped){ + c=cc; + }else{ + break; + } + } + System.out.println("CC:"+c); + // take care of title + String _title=c.getClass().getSimpleName(); + if(c instanceof CodeException){ + CodeException cc=(CodeException) c; + if(cc.getCause()!=null){ + _title=cc.getClass().getSimpleName(); + }else{ + // we do not have a cause + int code=cc.getCode(); + ResultCode rcode=ResultCode.get(code); + if(rcode!=null) _title=rcode.getSource(); + } + } + if(title!=null) title.append(_title); + // now take care of detail + String _msg=c.getLocalizedMessage(); + if(_msg==null || _msg.trim().isEmpty()){ + _msg=c.getClass().getSimpleName(); + StackTraceElement[] se=c.getStackTrace(); + if(se!=null && se.length>0) _msg+="\n\t at "+se[0].toString(); + } + String prefString="Exception:"; + String prefString2="Error:"; + int prefix=_msg.lastIndexOf(prefString); + if(prefix<0) prefix=_msg.lastIndexOf(prefString2); + if(prefix>0 && _msg.substring(0, prefix).contains(".")) _msg=_msg.substring(prefix+prefString.length()); + if(msg!=null) msg.append(_msg); + return c; + } +} diff --git a/src/main/java/com/reliancy/util/Handy.java b/src/main/java/com/reliancy/util/Handy.java index d9f9ac1..2a450e1 100644 --- a/src/main/java/com/reliancy/util/Handy.java +++ b/src/main/java/com/reliancy/util/Handy.java @@ -1,7 +1,11 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.util; -/** - * Common utility methods. - */ import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -19,18 +23,56 @@ import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.Inflater; +/** + * Common utility methods. + */ public final class Handy { - public static String wrap(String verb, String left, String right) { + public static final String WHITE=" \t\r\f\n"; + /** place left-right around verb.*/ + 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; } + /** remove left-right around verb.*/ 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; } + /** remove any chars elements from left of verb. */ + public static String trimLeft(String verb,String chars){ + while(verb.length()>0 && chars.indexOf(verb.charAt(0))!=-1){ + verb=verb.substring(1); + } + return verb; + } + /** remove any chars elements from right of verb. */ + public static String trimRight(String verb,String chars){ + while(verb.length()>0 && chars.indexOf(verb.charAt(verb.length()-1))!=-1){ + verb=verb.substring(0,verb.length()-1); + } + return verb; + } + /** remove any chars elements from right and right of verb. */ + public static String trimBoth(String verb,String chars){ + verb=trimLeft(verb, chars); + verb=trimRight(verb, chars); + return verb; + } + /** remove any chars elements from right and right of verb symetrically. trims whitespace first. */ + public static String trimEvenly(String verb,String chars){ + verb=trimBoth(verb," \t\n\r\f"); + while(verb.length()>1){ + char left=verb.charAt(0); + char right=verb.charAt(verb.length()-1); + if(left!=right) break; // left-right not even + if(chars.indexOf(left)<0) break; // even but not in chars list + verb=verb.substring(1,verb.length()-1); + } + return verb; + } public static T nz(T val, T def){ return val!=null?val:def; } @@ -219,7 +261,7 @@ public final class Handy { return bufs.toString(); } /** Attempts to take a user string and compact it to camel case. - * @param str nicely formatted string + * @param value more or less presentable string * @return nicely compact string */ public static String toCamelCase(String value) { diff --git a/src/main/java/com/reliancy/util/Path.java b/src/main/java/com/reliancy/util/Path.java index a58dde7..5c71891 100644 --- a/src/main/java/com/reliancy/util/Path.java +++ b/src/main/java/com/reliancy/util/Path.java @@ -1,7 +1,11 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ + package com.reliancy.util; import java.io.File; import java.net.MalformedURLException; @@ -13,7 +17,7 @@ import java.net.URLDecoder; * 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&... + *
 {@code 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. @@ -380,7 +384,7 @@ public class Path { * 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 + * @param _paths paths joined with colon or semi-colon * @return array of paths */ public static String[] splitPaths(String _paths){ diff --git a/src/main/java/com/reliancy/util/Resources.java b/src/main/java/com/reliancy/util/Resources.java index 3a747e3..fbe08a8 100644 --- a/src/main/java/com/reliancy/util/Resources.java +++ b/src/main/java/com/reliancy/util/Resources.java @@ -1,3 +1,11 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ + package com.reliancy.util; import java.io.BufferedReader; diff --git a/src/main/java/com/reliancy/util/ResultCode.java b/src/main/java/com/reliancy/util/ResultCode.java new file mode 100644 index 0000000..3b6fe72 --- /dev/null +++ b/src/main/java/com/reliancy/util/ResultCode.java @@ -0,0 +1,131 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ +package com.reliancy.util; + +import java.util.HashMap; + +/** Utility class to handle error codes and error messages. + * Error codes are integers that describe outcome of an operation. + * + * In cases when return codes are used and not exceptions thrown it is a mess to keep track of what they mean. + * With this class we define a uniform way of managing return codes and allow for additional information. + * This additional information can be text that could be localized and give better user information about what happened. + * + * First we distinguish between success and failure. Any code that is negative is failure. + * Default success code is 0 and provides no additional info. Any positive code is a warning or info and possibly carries extra meaning and + * or description. + * + * success,failure,pending + * source + * parametric + */ +public class ResultCode { + public static final byte TYPE_SUCCESS=0x0; + public static final byte TYPE_PENDING=0x1; + public static final byte TYPE_FAILURE=0xF; + final int code; + final String message; + final String source; + public ResultCode(byte type,short value,String src,String message) { + this.message=message; + this.source=src; + this.code=ResultCode.getCode(type,value,source!=null?source.hashCode():0); + } + public int getCode() { + return code; + } + + public byte getType() { + return getType(code); + } + + public int getValue() { + return getValue(code); + } + + public String getSource() { + return source; + } + public String getMessage() { + return message; + } + @Override + public String toString() { + int code=getCode(); + String context=getSource(); + String message=getMessage(); + if(context!=null){ + return context+"("+String.format("%08X", code)+"):"+message; + }else{ + return "("+String.format("%08X", code)+"):"+message; + } + } + protected static final HashMap codes=new HashMap<>(); + public static final int getCode(byte type,int value,int source){ + int st=(type <<28) & 0xF0000000; + int sc=(source <<8) & 0x0FFFFF00; + int vl=(value) & 0x000000FF; + return (int) (st | sc | vl); + } + public static final byte getType(int code){ + return (byte)((code>>28) & 0x0F); + } + public static final boolean testType(int code,byte st){ + return getType(code)==st; + } + public static final boolean isSuccess(int code){ + return testType(code,TYPE_SUCCESS); + } + public static final boolean isFailure(int code,int st){ + return testType(code,TYPE_FAILURE); + } + public static final boolean isPending(int code,int st){ + return testType(code,TYPE_PENDING); + } + public static final int getValue(int code){ + return (int)(code & 0x000000FF); + } + public static final int getSource(int code){ + return (int)((code & 0x0FFFFF00)>>8); + } + public static final synchronized ResultCode get(int code){ + if(codes==null) return null; + return (ResultCode)codes.get(code); + } + public static final synchronized ResultCode put(ResultCode c){ + ResultCode old=(ResultCode) codes.get(c.getCode()); + codes.put(c.getCode(),c); + return old; + } + public static final int define(byte type,int value,Class source,String message){ + return define(type,value,source!=null?source.getSimpleName():null,message); + } + public static final int define(byte type,int value,String source,String message){ + int code=getCode(type,value,source!=null?source.hashCode():0); + ResultCode c=get(code); + if(c!=null){ + System.err.println("Result code redefinition(consider different value or source):"+c); + return code; + } + c=new ResultCode(type, (short) value,source,message); + put(c); + return code; + } + public static final int defineSuccess(int value,Class source,String message){ + return define(TYPE_SUCCESS,value,source,message); + } + public static final int defineFailure(int value,Class source,String message){ + return define(TYPE_FAILURE,value,source,message); + } + public static final int definePending(int value,Class source,String message){ + return define(TYPE_PENDING,value,source,message); + } + public static final int SUCCESS=ResultCode.defineSuccess(0,null,"Success"); + public static final int FAILURE=ResultCode.defineFailure(0,null,"Failure"); + public static final int PENDING=ResultCode.definePending(0,null,"Pending"); +} diff --git a/src/main/java/com/reliancy/util/Tokenizer.java b/src/main/java/com/reliancy/util/Tokenizer.java index bd60852..123eecd 100644 --- a/src/main/java/com/reliancy/util/Tokenizer.java +++ b/src/main/java/com/reliancy/util/Tokenizer.java @@ -1,5 +1,12 @@ -package com.reliancy.util; +/* +Copyright (c) 2011-2022 Reliancy LLC +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ + +package com.reliancy.util; import java.util.ArrayList; import java.util.Iterator; diff --git a/src/main/resources/templates/error.hbs b/src/main/resources/templates/error.hbs new file mode 100644 index 0000000..5d13383 --- /dev/null +++ b/src/main/resources/templates/error.hbs @@ -0,0 +1,18 @@ +{{#partial "MainSection"}} +
+
+
+
{{error_title}}
+
+
+ +

+ {{error_message}} +

+ +
+
+ +
+{{/partial}} +{{> frame-1c}} diff --git a/src/main/resources/templates/login.hbs b/src/main/resources/templates/login.hbs index 784dec7..63130e4 100644 --- a/src/main/resources/templates/login.hbs +++ b/src/main/resources/templates/login.hbs @@ -1,4 +1,24 @@ -{{#partial "content"}} -Calling base +{{#partial "MainSection"}} +
+ +
{{/partial}} -{{> base}} +{{> frame-1c}} diff --git a/src/test/java/com/reliancy/dbo/TerminalTest.java b/src/test/java/com/reliancy/dbo/TerminalTest.java index ca4e5ea..ba1e2c5 100644 --- a/src/test/java/com/reliancy/dbo/TerminalTest.java +++ b/src/test/java/com/reliancy/dbo/TerminalTest.java @@ -1,10 +1,16 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.dbo; import java.io.IOException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; -import java.sql.Timestamp; import java.util.Date; import com.reliancy.rec.JSON; @@ -93,25 +99,38 @@ public class TerminalTest { @Test public void complexCRUD() throws IOException, SQLException{ System.out.println("ComplexCRUD"); + // Reading try(Action act=t.begin().load(Product.class).execute()){ for(DBO o:act){ System.out.println("DBO:"+o); } } + //Saving Product p=new Product(); p.setStatus(DBO.Status.USED); + Product.id.set(p,35); Product.kind.set(p,Product.class.getSimpleName()); Product.name.set(p,"myproduct"); Product.created.set(p,new Date()); - Product.short_info.set(p,"a sweet melody"); + Product.short_info.set(p,"a sweet melody:"+new java.sql.Timestamp(System.currentTimeMillis())); Product.display_name.set(p,"first entry"); - System.out.println("P0:"+JSON.toString(p)); + System.out.println("Update P0:"+JSON.toString(p)); t.save(p); - System.out.println("P1:"+JSON.toString(p)); - Product pp=t.load(Product.class, 35); + System.out.println("Update P1:"+JSON.toString(p)); + // Creating + Product pp=new Product(); + Product.kind.set(pp,Product.class.getSimpleName()); + Product.name.set(pp,"myproduct"); + Product.created.set(pp,new Date()); + Product.short_info.set(pp,"a sweet melody:"); + Product.display_name.set(pp,"created entry:"+new java.sql.Timestamp(System.currentTimeMillis())); + t.save(pp); + System.out.println("Create PP0:"+JSON.toString(pp)); + pp=t.load(Product.class, Product.id.get(pp,null)); System.out.println("Returning:"+pp); - //t.delete(pp); - Entity.retract(Maps.class); + // Deleting + t.delete(pp); + //Entity.retract(Maps.class); } } diff --git a/src/test/java/com/reliancy/jabba/RouterTest.java b/src/test/java/com/reliancy/jabba/RouterTest.java index 3922b0e..aa7bcd2 100644 --- a/src/test/java/com/reliancy/jabba/RouterTest.java +++ b/src/test/java/com/reliancy/jabba/RouterTest.java @@ -1,3 +1,10 @@ +/* +Copyright (c) 2011-2022 Reliancy LLC + +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ package com.reliancy.jabba; import static org.junit.Assert.assertTrue; @@ -31,8 +38,9 @@ public class RouterTest { //assertTrue( true ); System.out.println("Test router init..."); - Router r=new Router(); - RouterEndPoint rep=r.importEndPoints(r); + JettyApp r=new JettyApp(); + RoutedEndPoint rep=new RoutedEndPoint(); + rep.importMethods(r); rep.compile(); //Matcher m=rep.match("GET","/helloPlain"); Matcher m=rep.match("GET","/hello3/45"); diff --git a/src/test/java/com/reliancy/rec/ObjTest.java b/src/test/java/com/reliancy/rec/ObjTest.java index 7220270..6b5d3ef 100644 --- a/src/test/java/com/reliancy/rec/ObjTest.java +++ b/src/test/java/com/reliancy/rec/ObjTest.java @@ -1,10 +1,13 @@ -package com.reliancy.rec; -import static org.junit.Assert.assertTrue; +/* +Copyright (c) 2011-2022 Reliancy LLC +Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html. +You may not use this file except in compliance with the License. +*/ + +package com.reliancy.rec; import java.io.IOException; -import java.util.HashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.junit.Test; diff --git a/var/favicon.ico b/var/favicon.ico deleted file mode 100644 index 72b6083bd61f7d33994eb721138e6200d57f1eb7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32038 zcmeHw2Y8i5*8fX!Q9(k=O>XZky;n*ST4+K51r-riMVb_mmIO%XHINW`LP7$8&_WWD zkkGNA?&`Yh?qApatE?{SuKKATyNW{Y`~A+$eQ)j!NeIaL-~aP`_jx8$-YMtIoS8Xu z=A4NjbP~D>DJcScyRPBFBSwl z%Ag7%2aeMJ_xGRC1BVa4(k>w7kr`&6XQ5H;$BqQQ{$7n^lOX;R{rNuqe8yL*K*rdPX1|6{_VN9TFK5*C&< zU#*_^p_kVT;L33%e4Wlgeelpj(_D_yd-iv+Bt4{=AoGW|vn&}!};B^bG?s!v}aHsz0*>e_f zp9kC*iGDtrqQ76Z7$95eko&I@<^CH)ttwB92raB%z4DStYV_BWPD#OE)UT`iE!rE+ z+QhhYH5wP3=-G3+`}hLy=J9Pi%*#CV&`iLaUoR@< zxem2rE8KSU!)`G;;=tJtKmMc#M|zu3u$AEXWqxUC8(a-VMlPG}Ne_rh6^@8Z_y-@V^$q;3}%V_K|?rTJ4z(!Fe&lA;(ZK6iG6ZjW8w5q+LNmnKg zNU6@2fSLHj1u6J%^M?%C=&Bp**}Km5>#p#=9qr3yt3*Y>I^e$vxaR}+?V<)_uLJHn z^*)DQz2Bi#mWz>LC;oxiU@|kD$7=3;Dn72LN(;YPTX>OCF z^}eO!>9jUtE$lH1^gj>uzX_~zDm9sbYB4ccZgbLA#g8-D-{jugQ7um z*kMo~6Yb_|@wsPSi6#Ia9n(IKaX0Arner%sr#BY5Awi>kuHmwICD=(&6MD-U7+*F8D0I4~tSFF7%O z^W?CQ+*QGWo9pb>yz7d9T=W6a{w~mdkwd2{5%rq=a0f-B_NZvkRyvHDYSC(_6CZoz z)o}#qn31pD4VtZ%>DO-+OHWJNG{lL!liq0$W8=)mmcD5;^FFp(7q1&OY*U|BE4z!t zGZ*T7W1@5C*sZyLRt9VV{g)B$4uht`VbmTIjhZU-Lyg0vt#_EUr^T_OUYkVCj2rvL zU9{#|Bqe1HOBg(OeKc^Tac8)bPc%UDA2|ObI5=xQ#*)oJ_Zl{Bs%6o`hu?HhzHRu} zvEQ&f&plV>k$2sOJxYst@qUGTCFp_Hz-shCjl-y`5zV?&!2gWHsy{D2KJLxoRB*!h z*FDPLNkm%fGBOT(6D>&pf(z2JmrS`^;-z5cwsn3-JtJRCXr}7$Qsl%c_gT83QTxbwO?Tw<8@vATY zDFr*^$HwNce8BaHv~Hsx#Nop?rAQTw9-S`*nZJ5)K?)lIQv~OTOR5{GLdaMsMVnR&AWy;hifzLx%fp!8@rtD!~vDU{+V{S4nsF#Wi z9JpQzGCw9}%}j%S>E|?dG_Jnhz?b6+c$RYKw+#L=(cd?lJMgx|-!g~2PDbg7%UHgp z)KB1lmWuuQu2$!7hF@J~osKBqbVldfQR*vhr?fW@HQ1)OK~$z)^8a0VU>SHB$KwH$5* z@FBMnZdEIe(&7A;J819MYggaQ-*QgL8yh7Q6r6Ll+gczsh4;;{p*tV;mn{K2iyR97 zji7}SVxaY?YaX;#);;anXA4=>YF#*!=sq=dT^c3y?#=f=3S+!yDC8V(qHoYF8!y7U z8E1|w=V9plvKD;&T2T&uj+`Q3z0*;gr{lZBU$#cHTQ+?~?a*)hqs?m`Pigya+F(8( zh7a96nqa})as(Yz*bH)r@af059TS6*#*o0yZ!{o_JZMTynY1;ebsTuMMKbwB;KKj3*vD zAO$<@4^881V`}n_EMIRn*KmGZD_V^QI!psP-Mr^*w#5u-i#e0Eos_h0lCRHv%x`u! zchEY;=Qhn-C*C-#v2kYO>%Z1 zWOy!13J=Y_B=e=Yw;X-25j1d=AyZ%Xe#;^(K5pDr=7)r2R`U##objB}o;~SYzR*Ey zGp*TdEyLUnpm{Cf4PKp(GvU0M;|pBX@_dBzafaFIblcz;I2{WE|E=QFld2P_!Es|N zc=LDp_nmh>ah23-*E`pm6<7y$UgsaOUEf;<#*WPk#d@ca(rwlymwGxg#(W9Z zXlcype6ohqIMFv7Yr>n(suI3*ZXw5C8s@c$6n^dozHPv(;%B* z=W{;Z&3F^e9AA#-F2vz*e$^=VcsR46sKawpf~_O|sE7BlY{5bKf0FxSorQ}CFW{Sd zD;4b8)htU24b9o;lLA2WHBlivB-CXM+P&^V1b(KN@KaMq{@=iNB7?je~4o!xMD zaXK}8I7*9;Ikf0LFfPOF-+yCo zI*ar9&eu84tMIwb)^vIgW=w0JziAKNj(kh)x9=&1|7OrYt~hvL*~2Z% z3>(JzCzB~Fzn9mG#J6LQ!#YoV9PMu;hs3@{xGNd%w7${WM)F8b>r6Jo#+&di0q;rU zOgNXpC$}H&0COf?Ib=2!Vp9Bxs+O?)yW$4zHo%_;*=f;=w%VXQUXnjcdmNL)VeO#3 zjgPy8JLFM>JD=Z>NfyBsW4`YbHHv*=knNxt5pv|s*r;RMlY3W99W|mMWz~w`7(svc zySG0)taM}tD+H;xq*|6X}tzIY@4O!U|-P1nD);p&RmhbDB?@pR$vN$&G zdoYI!(FaAMSzjtfhE}|h7=Lv3y0vfVJA%m9U;nEk+3rc6_}J1+lB0zN7ro_P;lP2L zSyZLW_>S}Q65X%h`#Y1r6HmkEyOZV}w8p9Bg{W`;)qcH?-f}(_1hsJb^cDA}giDt` z^N3SKuc{P2`pCuh-tzrkB|I{wvM=!(f!4w+E$f{BV!4c+|&(}NJ z=WASoDf?(EHtoYO^~(vG-T9&3$oTgZhe9|l3VH#wwr4pt2uc* zHMwzsRHsRQLMSf!K#JZo-)8Z`2y3fZn0%678`RZe zkfrAP5hM<0F7stgG%72$4~gc|kIW3=D-v;EFv>~S{9!ydL>OzB%5 zM3r>yyb1JOCwM(rDuF4)L{vT#XgPXSuZBEcb433nl2ma&&+!-(#avC|0i!&Xq`FyWu=Tg09 zo47LP5}T(9v86&%JUgnelChCTrw2L+(Oj+Ke}F)}BPX7OqY- zYwO{-PjO18@6$B3i@Ui8sgj z6!0cFb%V=MoV$A757Y-b-Ul{gjriSnUon1RbMCuNXz$#sJNXeS6l^~z#zi0A(y|7< zZg~N}b0ylRHGhLc5wKZ|i7p&NdHW`n2tl^}tXOo!q387)+`*_1z|lR-5#Zex?lj&s z)-7=+{2P%j^#gsE`lb%`p`5c1!h>s_^VRb%q$Lg=i5s=$Vrt(L6I$lAT2?(rGFXyL zl01&~Jd0@?yM&33+#`gCa@<1#_ZJd>hx<3&JF+_{8hgRHBL)ShgXZM4MUcJb_`Z|XFPaxrhqJDQex zhY#CnB>Zu%$GES9EN+vS{_r8T$Hm3)GgNp;*)}?_;S7T@r?Ec-ybn9ap5sqz9qywr zkKM+a~zPU;JZ9(W%(*GKecSWw*$9Ok_(LGN&UKx*A%LAGN~S;G{! zay?jBrC4#3&;1Re*^vJ^B@*4!FFxi#I`OzT(>RFs4I0dQ;Lpb%>pkW@Tk9F_e4UeM zp5spAO?Z=k7VezWokbcssSn8KbAr|jwq9Z$=v7tX?3u5JGGOlVPfTP@aa4E}V^-dN z`wLeszxmBySlDb_w>1FrXX5qkmI5veaON^-Nmj9!onz_lncuy$bq~0^k3Gg8W6xoi zXx<%n;H@Lv&o?_3r#_J81kHyAe5W-5^8o9i0d&d%L=sO6^oc21)9U4^a7%}uL zThCf6zBse8r;=pWkdp-23nocz*6!^Zky|KIT1*8Hb(V zr+G~@Pq?|^t^@v(qrPzU0pU)0n`wa75#kFHq8sj-`13ZuYqXoHowP4u*6a`H&*kP; zbM7&@Y=hW8xzxpj8Fi#@L$uH4KJaGiK779Jd(yr;{(QY_rhDhO6W;m@&5q)X9?rF( z0ck%V{A;GuE+aPb3}d3FJk8gFwu(ka>|mc_ zB6qX7meQ~Map(B|O!{{l|2wCDhsAgjq{+_%ZNc7FdZ_)h`1xo5{XkpaZ%t1ZXdaa8 zedWQZ@RHsxy(6dIRm=5{(Leb8*4ID2uX(P2v<^CHfqd7x=)MmUzX1BDdwlTEF%hTO zzBPXypU=0#?3t22g6n+5MV_zh9e-&feVsI+WBNc7CwPj-NUjT6wu5BYddM?d?teJ* zFy2qXpUyFS|D*X!Yapj-&pvSC?OOY#wXb>oCw(5e!?&7h#rNL(yj_{KJN9GKUt`hn zV=u>t1)g)R`5WA`bSOM=LN&{0GZ+4W-C;2N8Sjhz-<|()=Y9G4ws{YvKA^F5?*n)K zhxP<%?{~+&0k{Ky?EMc9yEKvjd3@aM^%rJNV=Oy^JJ@OkFR*bxC0g`#;;LF3gI;=y3OzEq{WY(>iXZqDmyKFYMZae}z%4uw=K=W~XpILZ9#-lD zeg@#QK)7;e(w)IrpN6BkateEDy%-+U_~&WQye=o$hNWF>4@CCu*OQuQBQ&u7N}q(Y zjG-AixHwr^zT}1Hd^(I`FX;_J7uJvK4U(-3>FKEzn?;kp;O{o`j{Q;Ldp*`us(jUopU2a90Qhf;j2^wc z)tb{s?$X*VYf|mj^%LP{`}?k#Wi@Y{9c<5gD#)HUVARO{hSmyBoY1`PxAy%%J#E(W z&Ff`HV4eSB^ZwqE{QsNh?s@=gY+JvJo!A$LAS6@ZTS4eX{zW_}Kz8!%gya zqIg}<-4wi#y^APbL4hmDNJID{L)qUN>KEd0ylXT!hF{m)UH;-6A0BJ&yW*RHUr z2p_2;{4QdS4*o^2>jH9IyQsTD=v}**oI~hcy9l^Y(Y1@bp&zbL#mV9if4u@<=kh=Q z>qQp-=2{MXFH!P;M{pzlaTEDEmw$nEpH56>ezKU&{A>s7UO{YfcJq(uKkD{t)cpYS zbKaNY$D()>eT?Fwh`zm<4Idr+9X#<{ow$~K0IVY)H8ZdEKX!l>0XG@$mv|>)&YUtK zI5@MDPWNIL*w%CvgoMs$+U+;4i`kUf)nr=MB`!9XD2;uCeWzQVUp5#MLqyR24{&yC zKZhV-ji;fwUABAn9k!OY)HQM9E}y`_j9;Vwi($|73T(DtgKgW}uy=U3SFh>6Mf_E@ zdf|DKaoM(*=#66l@2^@`@=M?Vn^DjP{{;qS>G5vDvkJxB_aqy0$(~%YHKjKO*c%Ex zW^fyNAF`p(0uQya$!5uYE!e(&%+r&qe55*KA}YF281~ar(mnBE6YZ*J)Tk{o*ha4Q z^LzeZ5}Y_AF*`c$(|L*BeQ?YLVABk^N3nAhAz(HyoC&)4hh+Z+7=I!-IlK&Jz|G)iX~+kA(CIk)%yGbm zopfg%q&LfTDRP^jclo0-UF*p+`mwBk-D5}agz>*qI1 zl*wlQ1#|ZtT&Ys|LILnyhwvJOeBMU2dhvFZa_J$hdc`HZcHLF2dK2z*NpBOj7OKg`itr#A7?zQUbD^3~O3COzN?jR16*_e$PlP3qQ|6S^5FR$q$ z#^D{nx_-ih-F_Xc?6%p44P6$f)hs=rQRRKFRuB!H`U$qGqEUTXj0n1Txp`i%6L8nr zZhct4+4r40&vXS%{P}?go@4!uzP@ZWFM6uA!pD!l^dM{&2VrfWqf%yOlg=aDKBXe_ zaDactVU;4Y0`a|SRpvUSV(~JwdFj;fh!sgIR~_r&USDkdv*BuW#yR#TiSh_!w-;}a zOgl2F+ zKLr?zhMN}sDKS2}fi2)Rb0ym!++mTzAt4!z<}H?mBZ$T^|6YxcU#^zEi{dw3^!lt} z2K|yte!koWnA!E>T?blM{Cr@W=(7y=NIB%7a|s{%#$>xoI_Eea|3rCIitI09V>gV1 z)UV6nv@NVn*gFdkJTS|(*7WbcCfVPA{x^O;IigN^ShSmJe@q`)7fi4W>VNCrrq#Z{ zzyz&z>eXw;ci2}w4rVsnvUH67s{}XMFEcwoz{2cv0Uzw$V57MTc9qzdpeGpMzs5nf zRb*$0IKHn!d=>0h*SvG4@jbS#Nd3~gck>-wROA{dSr|F8FhU-X@wHE{OvpO+i6QnX zmv9U=d#-#G7&?@%_n`TeYV`{h(#O+{FF&YKF0S|Um2AtieuO?_6Vqv9wzq%_@X>xm zat7$QK+kKVLm}S?c?{M_=)1u+k?xC8pZk_nugSQY(Luj{o~j4^Q}dizY|I}tVD~^p zK(^@PwFLS)MPgiJeI$MG>aovh7t$|TdD~I{Z7W{)`Wkk~) zZlVk5BBEabGhk)*ln#0aM+v<|*i({TuH;BR8?bL7xyco&!H~esEqH6`OS2DKSwu!K zdI}HSJU2kL9PO`!41aH%a~bx5y~D$o#8EAF+miXdzVn&=KJEF?p=A3$(<3Cf(Vt{< zqyo&OM+`WjM^AQ;+*XX%bl4LBk8KXUc854*P(|-%=sFg1=SLw+yWr=&9CWl%T)pC* zUR1;Go$tCTnml<|m&J?AeJR0a%bG#^xIK~ykiUDxY>c3brDbj(vp2Gv z54_9)*z8*0dA#F)`==JM%*lO<2WpgLtDO7i)=Ddt3wIEY4tXxt7)CRomo;oX0qlUA z-YO#4sm}qsg6xC9djVdiKZmmgfHV9!VM5w!9X$=iuO zOgg+Zq%C{jZZ3Z{BJ@Z>LhSKL!v@zUY*_ny|MRD=bx9xk2Pb`ov;^ml<5A&74E|vI zo_x}~j|?kf_x~p25|*yfWE4ntDqLTg)#fWO(^B{U?*;_I{MD!t1GxZowI; zK-6h=eH9c~{7h?gM}}2dU=!0j$X2z)W~!>U8LL0F8o=o0E+Cv|hbVmU5F*xWbsjGg1UdBzpivEUu1=*W0+mkxdvqHF*(tgqd57Obcn`++0 z-pcwv*mlx6$8QTd%b*G<=@w)=09%uTzm`fE^w}fvrppa~-1`t8MmqKeU4aLa8XtGa zXfYgk3ujd5nH>hqOurOoy(+-y)U5}cCQUsY^rJM;DbYYziun}hI^%3ifJdOY=DqVT zy~ku&=_4*kpxoB)et%*25c|QelH!i^lQP?ED`Nv>%f2SsAzONlYReA~k2>m-DTM~^ zA8*i>{-9Hlel*zzkWCEN2Z3%J`WAGefvpe02zZT5FHF)2!a0!Z|2XlgLs`IF4 z63(z6bl|06tNv@R{FSBmO{`LwbO-;qdnf0CC{Yyu(Ir5Z{T_I9z)w2v+vZD^M1+=& z)~jhxg?^v<;0>~CfISbv&uozx?9jzy{m%56@XnOxG&$%oF(2QU-Y(Z;qOpPQk6vBf zWH+A_`zAJqQCqTJ$E7A)^+!G2z~)S9^2rf)%hBIZCa>(HRsry zSxg^GL%LXaKZ?;5(GunZ=6hV!Dd##)Ew=qj?pw$9si^SkU2!or8!2Z>-@49Gky{vE zVWFE6<+8PaKlhb!V-K)1sX<$KjN4X}HbIvHb~XH6H%YIWXn@&>G1`WH9QC<|>}Jk6 z-Ff)rNS6>gWo#TkS8NRGnrJ>pgw!2w8*2A2B@F)1!0LDVB{eYK;mOBecF!Pal(g z>4Y-6;`+}-L-kFx&V~loF<#OGkQNc>ys?x%iH&`u!|G4s^&pQNHuDA+*J*P50RC4g zVlx*#qG7s?dw+7m54|qDsmN%b+3*qVfF1~bMmr3C2D_HQ4Shw12Xr+7zve8XE1C;N z4e5Sz_#;B<%RPW;ClVS|zdtGd%!HO%tY&`prF78#Qs`=j1ljk_XB)cTm|9tLZ z4saVpn!8T;fdiv`z{P1t(nBWLiH6J?q9Hzyo$&ue<->#PC$)sfJ)RiD*VfR$hVZz^ zvvpgye!%D`eZ)ogY(nn@end)$-GjFuZ*;L+3TJWM(&9Ggr^Mb%F7meqPmV&bG`G8@1b& zu2b*qtD`qvR7F3LY?9_L)_$xzeC>x#6oa4XL9+eIef?oLU~Je}@HNN2ad)&{u5?oI$8ADa$SXBV#zrkkYTrq;z8Yr~ zuGh(IdqDqAn?BHxw0Ak@FzAHd*K^X5b1c}HG{a775AxN6e?PTVYJBKm&nXODO(8Oj zulH7CePTq&8IpmrzFm^_N2%CteT%7ZhK{{2Q;hyEL;sh9_79`qr8S7Zk4gLu?Y&0E zD>!veX|0qTqkCF6>qVRKR8w?V<7}z%^=n&$ho2Yirjx;up$&f>)c*ooFL{}DQqeYj z=vCueF2mQTFDlk@`??!^4JH18Wc0_d9#w*l84j?WsfE0?{s-ttU$q(<{%qDY{LX4P z{TAGt2)|}GHJphEZk(Di|F_+xhGQZcr6BW1k7^Vi?7ENbr&d!{4D3s<_DMWFM9Kuc zX(@PHfAGMAt~@4vaV67@rT0nM9RuxSpdTH0Vq?wAunAooY^$9a8PYHqYlMBqv^Tt4 zqi0uz2v`mE0`#C+66|i>KMCO>mE%JKt3FPQts6ieq9bZM_lf6wfJer5BRauq+Uv?; zG3?9LYv|n;;Gn9&J0^vq-L`d>D?^)sDT2RuZ5QY;ca01=-Fa2^?_D{2CwRgQJHiKo zZHH@vZO1MRO>59m#^~_+JL0chl_LP1TtOw@>sphdBgzv@x&yz}fuAFN%2uB(>iGwZ*t+3T*f)tfrEGT8yP<5@HUAyJv^)tb6pX!_bFiWg|ilb@uAM z!8M*L)q)h2a^W5y?-&0`awEEzkUdP{M$RWVPkbl$U-2)<{I4nHYrj>>*MFu`YFk z;9bO_i`j0exaiG_u#k;G&=Uxa49gGflejCOtpbom3X&~&TY3K7>1NZiPFn4fE|6n* z%uUaBpX_AR<#)B3ws($+EM}h|SM$trhcT?v_|X|s^1toI1^+s!JHfv~ zdR*`?l3o}5oNQ4bpH#uJ1;L@a?)7c5AlyXwCi&NeE)Ja_{CEZGPLR}H%Rx1ei2guY zOqY?Ls)x^-`CJgtOt$!`AbiWTvjhRnK}(B#9kjLt;UZ}c-GEPOQsgIJ(J_A+>Zdr` zBmWYqd%=GhaFTyH$LS03QO{@avjK$Ue^2pm7(9X~`5&`*CsFb*pe(6vWiwyrKt#+* zhE^B+O8}4jZ?nbAeoxkL`&I0svpl9LL6K}xbK2oJvjr+j7@~0+Lnx(=6QmUk2hkUME6VLME zZTQi+cXjHf&>Q+pKZSHuSQs#n4iMz?3vs8D&F)0ly_tL`-cu{Pi2h>pK5fA14z=B^ zA0r6pQ1kl(i!W(zKD_DZ_4|E#F zDdZVDl!~mYa({Xk9rx|%bGnzIFx_z|0y0Fr$@bCA=l2IlWus%}#rn&#AQwH(Z07JT zkX-uK(l?tI^dnss+&eQFWl3?}{kYrTN4gQDD?z&Wbbm$iWEyLd=}Qj!39#8%@ge$( zo#RtebMfx;DpwQGIrvKHUmylqPyQDrMn^P{waLWAig-W!ic~M7%Ey~#6UFNso8q*2!p*t6T-^K5Y=xr~$OUJ!5?f@ib)ECflFQ}F4T2{;A z{LY;0aZ6?Xcqh$d$p0sO7&@fPm4DW(V%gB)8@%a$*@-jLjUZgveK_6CI^U0E@5#CD zXLpG&%sbbMDsnim>$UNd?u>&1^Va+MtP-Dk>bPrt5(GBf0^Q5I+Gg+C(0i@)jvf17 zB6P=xyHn`2v9}HOLMFMF))*#_kB=*DqvczZ*?SSDyd3(1|6Rp01O&`K!S!&KG2IY; zPsH!I_&qee2g~1z)GA8>2kc3;`$gO>KF2EPz@O>UPOw}iT|uEqzx5@l$g|JncOzX2 z@TchWmF#YlbR{&n2Pat*;cGMR|1j8gaBV_NW%~3fFDs<7OgLXH#2L8ht>9P!WT=YgU zXbrlT1tQ+;E0MmFKRW$XfYn%;VKtsOXVF*PFyo9)@AA<-9Nye|JvOp-DCJYhjtlMG zz2$9rld|D$tbMNb74jutl3o*VZ=!dBqasR@SSI`lwpFBC3`aiWcT&yrNhV`u_fK>O zRRj3}?rtnKta6L?_WLdFnq2M61D0N4Z@9@eLicOeFsW^vncmZ?=xzXaO1Kx`_emt% z<@Yg=`IFp=#{mo7=i%Oq9psbZ8l-AG{K-#UVDZqvLmx^Xl!}}Rx}(r*^4_4dNfVDh zs8`e7Kgp$zkUR%6D3S}pHk#U|dmqT4nB0|QQ6z_AaMVGT#oqlHE!EwcUt0F3o;K4F z7lW_Utb97aZ!PXVx3LWnZx{U+mrc@pT_l&JaVD8Cwe7r5z#9Z4hvfGm1OvN2K)#c( z%RX7tTDwwOXi(LQDShkv@%VV=De=Dl$k!*)-3FJ-9m2i9al+l%KFJ74j)=BN zc1tp7CWl6t^#k4O)!|LVli>7k{o!2`mKYmRe~JQtLBJbEEX}Cf{De`Lmxy^$tmEx7 z{(;^pqq|#vAB6XHSUdDK3AN2+$><0E1`XM#)4dC1R$KtZYWDmy@rhTvM};?Ra8I?G z_SEED!l>B?gU%n&&ItOe+CEj1!Sote>ZAsv1Zh|U1@csu2 zLdM5@z!e-9eTLnO_DgJ?59RVz*hB}GM3L?@{l75wW*Ny;X{`etSdDczAnScO$kKQq z&~kb!+$P9Smkmn2V3gX%8x$-U8G4NQLAL6|={Dx7$=+%b^uuP`EeHM{6>*gD8S%00E(y2P(NcB`JK=mEpBKUF(*OI(`dA^)*#w)| z={D=8VbH%!15Y(suPIok*X(%DWGMU+cF#{rm4=5Lk%D*Z!$#SoOmNWhFvt^^C<2z1 zLKgQL;)u z!+_lUH)F=W2XSWV4ZZN>=%|$jDP0hH-eRl6&$dsoET|(5=ew&;yBeko?}IarO0kjj zsWPN`5~BHj&EuS;a6UZgJRklYvWG+7-ZOH1eP=I$&cSZ2I`2~r-n+CIs=s#Cf%6`0 z8CVo$FTk={T5~6|LE9*LuYE$${nBKjs{|@<{#u8L)Ge8 z5lThI6Efe7n|?TpfnQ~O8}Sb+^DsM13RVon6YaEWPS_Zq;njzvvK~P zK+^X#Xm`BL^UW5Llk=n^f=aQ@#V6rhbb`)ebnfE+w}E)_66{r_VrQKy)$G7&HSo1&_z-rr2|K#7O-lLF3G_dNsH{<2 zc0teH;zK#c%aM)&-S5?s{?B^KR!okIU4B3GY)?RMWCPu|;hqZn9o{X(-ox&} zu?N$>WHp@p!M$u`=*fpCJaOqhN{a~1H|o`ef7RnHOm@!&UV-Ul@Vj*IE~inx_6p^V z4CQwWv5{w=pm?BlD|rI;#-q>!MR|HRnD#^B$7uh>9XI};TWoZ_jb+52=&;jcV#8m# zUy4gUc*`$nU&CG5HoK{DVzBjGKkR1-(s%MhgN{EF6H&+JQ%v}I_Q`50c4bj1Hq7+# znf)pB{#U4!*|8S$I<-N&H34@I^KIrbF3dn~?0c><6ySFrBVB1)O_mk!8(?hKPSI=g zQ(PYq2n%M@ge8%T^STc5TI@h?zX&;QB4n~l@&C$^z7~ttu6}~B58rd(E|N