├── .gitignore ├── LICENSE ├── ObjectiveSync.json ├── README.md ├── build.gradle ├── classes └── ch │ └── loway │ └── oss │ └── ObjectiveSync │ ├── CS.java │ ├── Column.java │ ├── JdbcPattern.java │ ├── ObjectiveFetch.java │ ├── SqlTools.java │ ├── table │ ├── SqlField.java │ ├── SqlTable.java │ └── type │ │ ├── FuncValue.java │ │ ├── IntValue.java │ │ ├── StringValue.java │ │ └── TableValue.java │ └── updater │ ├── FieldSet.java │ ├── SqlFieldVal.java │ ├── SqlUpdater.java │ └── deferred │ ├── DeferredLoadByParent.java │ └── DeferredLoader.java └── tests └── ch └── loway └── oss └── ObjectiveSync ├── CSTest.java ├── JdbcPatternTest.java ├── ObjectiveFetchTest.java ├── ObjectiveFetch_1N_Test.java ├── ObjectiveFetch_ReadOnlyTest.java ├── learnH2 └── LearnH2Test.java ├── maps ├── ACInfo.java ├── ACInfoDB.java ├── Organization.java ├── OrganizationDB.java ├── Person.java └── PersonDB.java └── table └── SqlTableTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Package Files 4 | *.jar 5 | *.war 6 | *.ear 7 | 8 | # gradle 9 | build/ 10 | .gradle/ 11 | 12 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 13 | hs_err_pid* 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /ObjectiveSync.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": "loway", 3 | "artifact": "ObjectiveSync", 4 | "license": "Loway - All rights reserved", 5 | "depends": [ 6 | "org.slf4j:slf4j-api:1.7.7" 7 | ], 8 | "testdepends": [ 9 | ] 10 | } 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ObjectiveSync 2 | ============= 3 | 4 | A thin Java object persistence layer for JDBC. 5 | 6 | 7 | **Downloading** 8 | 9 | This library used to be on JCenter. Frankly, I cannot be bothered to jump through all the hoops to publish on Maven Central - I do this in my spare time, give me a break. So you can build it on your own and publish on your private repo - I enclose a recipe for PomFrites https://github.com/l3nz/pom_frites to make it easier. Enjoy. 10 | 11 | 12 | 13 | 14 | 15 | 16 | News: 17 | 18 | * Feb 21, 2015: Version 0.1.4 will have a working Connection. 19 | 20 | Ideas 21 | ----- 22 | 23 | Great things in Hibernate: 24 | 25 | * Association mapping: loading and saving associations in one easy step 26 | * Database independence 27 | 28 | Not-so-great things: 29 | 30 | * What you get from Hibernate is not POJOs. Need for DAOs and copy, copy, copy. This is useless code and often breaks. 31 | * Configuration too complex. You end up modelling everything around what works in Hibernate, not your classes (as promised) 32 | or what works in the database (that is the real constraint you are facing) 33 | * Hibernate-aware code everywhere 34 | * Different query language without a reasonable shell - you have to write code to test out a query 35 | * Very complex and inefficient queries triggered for no apparent reason. You spend a day to control something 36 | that would have taken a few minutes in SQL 37 | * Slow to start - big problem for testing. 38 | * Bad practice - if you hide the database, you may get something done quickly, but it's a bad idea. 39 | If yor Java code expects to have a collection of one million objects as an array, it does not matter 40 | if they are lazily loaded or not - some code somewhere might want to iterate over them, and this will 41 | kill the process. You cannot really forget that there is a database somewhere, and you should not do it. 42 | * Aborts on commit. For long-lived transaction, you never know WHAT made the transaction abort. And what can you do next? 43 | 44 | Aims 45 | ---- 46 | 47 | * Minimal wrapper over JDBC. 48 | * Querying done in SQL. You should not be afraid of SQL. If you are, you should not be doing anything above the trivial CRUD. 49 | * Centralizing object marshaling and unmarshaling - each object should know how to sync itself and its descendents 50 | * Single syntax for inserting and updating 51 | * Ruby-like objectivized JDBC fetching with exception handling 52 | * User-definable deep fetching and updating (almost Hibernate-like). 53 | * Batch API to avoid round-trips when submitting multiple queries. 54 | * Stats collection and similar stuff. 55 | 56 | Downloading 57 | ----------- 58 | 59 | If you use Gradle (or any tool using Maven dependencies) you can simply declare the lib as: 60 | 61 | 62 | repositories { 63 | mavenCentral() 64 | mavenRepo(url: 'http://jcenter.bintray.com') 65 | } 66 | 67 | 68 | dependencies { 69 | compile 'ch.loway.oss.ObjectiveSync:ObjectiveSync:0.1.4' 70 | } 71 | 72 | 73 | This will download the package and all required dependencies (that is basically only the logging framework). 74 | 75 | 76 | Getting started 77 | --------------- 78 | 79 | You can see examples of simple operations under the 'tests/' folder. 80 | 81 | * Object are POJOs. No need for importing interfaces, annotations, etc. so they are easy to move to the client side using 82 | GWT or Jackson. 83 | * For each object, you create a database accessor class, usually called "ObjectDB". This class extends ObjectiveFetch. 84 | The ones we use for testing are in "ch.loway.oss.ObjectiveSync.maps". 85 | 86 | The database accessor requires: 87 | 88 | * a **table()** method that specifies the fields for your table 89 | * a **load()** method that given a recordset row will build you an object and will schedule DeferredLoading for 90 | additional objects. So for example, in the Organization class you load all organizations and schedule 91 | deferred loading of all Persons who belong to them. This means you avoid the m*n problem when joining tables 92 | and that in the future we can schedule deferred loading to be efficient (e.g. batch, or in case you are loading 93 | the same record multiple times) 94 | * a **save()** method that will save your object as a row on the DB. If you do not need to save, don't implement it. 95 | Columns have valid defaults for insert and update. 96 | * a **saveSubObjects()** that will save dependent objects. 97 | * an **updatePrimaryKey()** that will be triggered on inserts. 98 | 99 | How it all works 100 | 101 | * PKs are strings (or numbers) and are created by the database layer. No composite keys supported. 102 | * When loading objects, you can either create full queries (for aggregation queries, e.g. avg() count() or whatever()) 103 | or pass only the "WHERE ... GROUP BY ... ORDER BY ...." clauses - the object knows its own fields. 104 | * Whatever goes wrong raises an SQLException 105 | * The library is meant to be easy to use and to replace Hibernate in simple cases. When Java and the DB think 106 | different, the database wins. 107 | 108 | We have a group for ObjectiveSync on G+ - https://plus.google.com/u/0/communities/108551537631226271215 - come and say hi. 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // build file here 2 | 3 | 4 | apply plugin: 'java' 5 | apply plugin: 'findbugs' 6 | apply plugin: 'maven' 7 | def env = System.getenv() 8 | 9 | // 10 | // I defaults valgono per HUDSON 11 | // 12 | 13 | project.ext { 14 | webapp_name = 'ObjectiveSync' 15 | app_version = '0.1.4' 16 | build_number = env["BUILD_NUMBER"] 17 | version_class = 'ch/loway/oss/ObjectiveSync/CS.java' 18 | build_time = "" + new Date() 19 | } 20 | 21 | 22 | 23 | repositories { 24 | mavenCentral() 25 | } 26 | 27 | configurations { 28 | ObjSync 29 | } 30 | 31 | 32 | 33 | dependencies { 34 | ObjSync( 'mysql:mysql-connector-java:5.1.30' ) 35 | ObjSync( 'org.slf4j:slf4j-jdk14:1.7.7' ) 36 | ObjSync( 'com.h2database:h2:1.4.178' ) 37 | } 38 | 39 | 40 | dependencies { 41 | compile 'org.slf4j:slf4j-api:1.7.7' 42 | 43 | testCompile 'junit:junit:4.10' 44 | testCompile 'org.slf4j:slf4j-jdk14:1.7.7' 45 | testCompile 'mysql:mysql-connector-java:5.1.30' 46 | testCompile 'com.h2database:h2:1.4.178' 47 | 48 | } 49 | 50 | 51 | 52 | task solveDeps << { 53 | 54 | copy { 55 | from configurations.ObjSync 56 | into "$buildDir/dependencies" 57 | include '**/*.jar' 58 | } 59 | 60 | } 61 | 62 | 63 | 64 | 65 | 66 | sourceSets { 67 | 68 | main { 69 | java { 70 | srcDirs "$buildDir/classes_subst" 71 | } 72 | } 73 | 74 | test { 75 | java { 76 | srcDirs "./tests" 77 | } 78 | } 79 | } 80 | 81 | 82 | task writeNewPom << { 83 | pom { 84 | project { 85 | groupId 'ch.loway.oss.ObjectiveSync' 86 | artifactId 'ObjectiveSync' 87 | version project.ext.app_version 88 | inceptionYear '2014' 89 | licenses { 90 | license { 91 | name 'GNU LGPL v3' 92 | } 93 | } 94 | } 95 | }.writeTo("$buildDir/libs/ObjectiveSync-" + project.ext.app_version + ".pom") 96 | } 97 | 98 | 99 | 100 | task sourcesJar(type: Jar, dependsOn: classes) { 101 | classifier = 'sources' 102 | from "$buildDir/classes_subst" 103 | archiveName project.ext.webapp_name + "-" + project.ext.app_version + "-sources.jar" 104 | } 105 | 106 | task javadocJar(type: Jar, dependsOn: javadoc) { 107 | classifier = 'javadoc' 108 | from javadoc.destinationDir 109 | archiveName project.ext.webapp_name + "-" + project.ext.app_version + "-javadoc.jar" 110 | } 111 | 112 | 113 | 114 | jar { 115 | manifest { 116 | attributes("Implementation-Title": project.ext.webapp_name, 117 | "Implementation-Version": project.ext.app_version) 118 | } 119 | archiveName project.ext.webapp_name + "-" + project.ext.app_version + ".jar" 120 | from( "$buildDir/classes_subst" ) 121 | } 122 | jar.dependsOn writeNewPom 123 | jar.dependsOn sourcesJar 124 | jar.dependsOn javadocJar 125 | 126 | 127 | 128 | task setVersionInSources() { 129 | doLast { 130 | def cjd = new File( "$buildDir/classes_subst" ) 131 | cjd.mkdirs(); 132 | 133 | def FileTree tree = fileTree( dir: "./classes" ) 134 | //tree.each { File file -> println file } 135 | 136 | copy { 137 | from './classes' 138 | into "$buildDir/classes_subst" 139 | } 140 | 141 | ant.replace( file: "$buildDir/classes_subst/" + project.ext.version_class, 142 | token: "VERSION", 143 | value: "VERSION = \"" + project.ext.app_version + "\"; //" 144 | ) 145 | 146 | ant.replace( file: "$buildDir/classes_subst/" + project.ext.version_class, 147 | token: "BUILD_N", 148 | value: "BUILD_N = \"" + project.ext.build_number + " - " + project.ext.build_time + "\"; //" 149 | ) 150 | } 151 | } 152 | compileJava.dependsOn setVersionInSources 153 | 154 | // decide what to see -only one can be enabled 155 | // get the HTML report from build/reports/findbugs/main.html 156 | tasks.withType(FindBugs) { 157 | reports { 158 | xml.enabled = false 159 | html.enabled = true 160 | } 161 | ignoreFailures = true 162 | } 163 | 164 | // Force sources to 1.5 165 | compileJava { 166 | sourceCompatibility="1.5" 167 | targetCompatibility="1.5" 168 | } 169 | 170 | -------------------------------------------------------------------------------- /classes/ch/loway/oss/ObjectiveSync/CS.java: -------------------------------------------------------------------------------- 1 | 2 | package ch.loway.oss.ObjectiveSync; 3 | 4 | /** 5 | * 6 | * 7 | * @author lenz 8 | */ 9 | public class CS { 10 | 11 | public static final String VERSION = "1234"; 12 | 13 | } 14 | 15 | -------------------------------------------------------------------------------- /classes/ch/loway/oss/ObjectiveSync/Column.java: -------------------------------------------------------------------------------- 1 | 2 | package ch.loway.oss.ObjectiveSync; 3 | 4 | /** 5 | * 6 | **/ 7 | public class Column { 8 | 9 | 10 | } 11 | 12 | // $Log$ 13 | // 14 | -------------------------------------------------------------------------------- /classes/ch/loway/oss/ObjectiveSync/JdbcPattern.java: -------------------------------------------------------------------------------- 1 | package ch.loway.oss.ObjectiveSync; 2 | 3 | import java.sql.Connection; 4 | import java.sql.ResultSet; 5 | import java.sql.SQLException; 6 | import java.sql.Statement; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * This class encodes the correct JDBC handling pattern, with all resource 12 | * reclamation and try/catch/finally logic. 13 | * 14 | * Plus, we offer a few ready-made 15 | * static methods for "vanilla" insert, update and exec. 16 | * 17 | * Note that there is no "vanilla" select as this is something you really 18 | * should customize for general usage. 19 | * 20 | * 21 | * @author lenz 22 | */ 23 | public abstract class JdbcPattern { 24 | 25 | // logger is public so children can use it 26 | public static final Logger logger = LoggerFactory.getLogger(JdbcPattern.class); 27 | public Statement stmt = null; 28 | public ResultSet rs = null; 29 | int nRowsUpdated = 0; 30 | String insertKey = ""; 31 | int durationMs = 0; 32 | 33 | /** 34 | * Here we perform JDBC access logic. 35 | * 36 | * Tip: the public Statement stmt and ResultSet rs are 37 | * closed automatically if defined. So you should use them 38 | * to avoid wrapping your own code in another try/catch/finally block. 39 | * 40 | * Let any errors bubble up as SQLExceptions. 41 | * 42 | * @param conn 43 | * @throws SQLException 44 | */ 45 | 46 | public abstract void performJdbcAccess(Connection conn) throws SQLException; 47 | 48 | 49 | /** 50 | * This methods performs database fetching. 51 | * 52 | * @param conn 53 | * @throws SQLException 54 | */ 55 | 56 | public void run(Connection conn) throws SQLException { 57 | 58 | try { 59 | 60 | long startTime = System.currentTimeMillis(); 61 | performJdbcAccess(conn); 62 | durationMs = (int) (System.currentTimeMillis()-startTime); 63 | 64 | } catch (SQLException sqlEx) { 65 | logger.error("Exception when running: ", sqlEx); 66 | throw sqlEx; 67 | } finally { 68 | 69 | try { 70 | // Close the result set, statement and the connection 71 | if (rs != null) { 72 | rs.close(); 73 | } 74 | if (stmt != null) { 75 | stmt.close(); 76 | } 77 | } catch (Exception e) { 78 | logger.error("Ouch! Exception in finally block!", e); 79 | } 80 | } 81 | 82 | } 83 | 84 | /** 85 | * Ready-made insert updater. 86 | * 87 | * @param conn 88 | * @param insertQuery 89 | * @return the object, so you can fetch the insert-id. 90 | * @throws SQLException 91 | */ 92 | 93 | public static JdbcPattern insert(Connection conn, final String insertQuery) throws SQLException { 94 | JdbcPattern pInsert = new JdbcPattern() { 95 | 96 | @Override 97 | public void performJdbcAccess(Connection conn) throws SQLException { 98 | stmt = conn.createStatement(); 99 | nRowsUpdated = stmt.executeUpdate(insertQuery, Statement.RETURN_GENERATED_KEYS); 100 | rs = stmt.getGeneratedKeys(); 101 | 102 | if (rs != null) { 103 | while (rs.next()) { 104 | insertKey = rs.getString(1); 105 | } 106 | } 107 | } 108 | }; 109 | pInsert.run(conn); 110 | return pInsert; 111 | } 112 | 113 | /** 114 | * Ready-made update query. 115 | * This method raises an exception if the number of updater rows is != 1 116 | * 117 | * @param conn 118 | * @param updateSql 119 | * @return the object after running the query. 120 | * @throws SQLException 121 | */ 122 | 123 | public static JdbcPattern update(Connection conn, final String updateSql) throws SQLException { 124 | 125 | JdbcPattern pUpdate = new JdbcPattern() { 126 | 127 | @Override 128 | public void performJdbcAccess(Connection conn) throws SQLException { 129 | 130 | stmt = conn.createStatement(); 131 | 132 | // Execute the run 133 | nRowsUpdated = stmt.executeUpdate(updateSql); 134 | 135 | if (nRowsUpdated != 1) { 136 | throw new SQLException("Update failed. Rows returned: " + nRowsUpdated); 137 | } 138 | } 139 | }; 140 | pUpdate.run(conn); 141 | return pUpdate; 142 | 143 | } 144 | 145 | /** 146 | * Runs a generic, no-reply query. 147 | * 148 | * @param conn 149 | * @param anySql 150 | * @return the object after running the query. 151 | * @throws SQLException 152 | */ 153 | 154 | public static JdbcPattern exec(Connection conn, final String anySql) throws SQLException { 155 | 156 | JdbcPattern pExec = new JdbcPattern() { 157 | 158 | @Override 159 | public void performJdbcAccess(Connection conn) throws SQLException { 160 | 161 | stmt = conn.createStatement(); 162 | 163 | // Execute the run 164 | stmt.execute(anySql); 165 | 166 | } 167 | }; 168 | pExec.run(conn); 169 | return pExec; 170 | 171 | } 172 | 173 | /** 174 | * A quick-and-dirty way to run a set of statement, e.g. to create tables. 175 | * 176 | * 177 | * @param conn 178 | * @param statements 179 | * @throws SQLException 180 | */ 181 | 182 | public static void execAll( Connection conn, String[] statements ) throws SQLException { 183 | for ( String statement: statements ) { 184 | exec(conn, statement); 185 | } 186 | } 187 | 188 | } 189 | 190 | -------------------------------------------------------------------------------- /classes/ch/loway/oss/ObjectiveSync/ObjectiveFetch.java: -------------------------------------------------------------------------------- 1 | package ch.loway.oss.ObjectiveSync; 2 | 3 | import ch.loway.oss.ObjectiveSync.table.SqlField; 4 | import ch.loway.oss.ObjectiveSync.table.SqlTable; 5 | import ch.loway.oss.ObjectiveSync.table.type.StringValue; 6 | import ch.loway.oss.ObjectiveSync.updater.deferred.DeferredLoader; 7 | import ch.loway.oss.ObjectiveSync.updater.FieldSet; 8 | import ch.loway.oss.ObjectiveSync.updater.SqlUpdater; 9 | import java.sql.Connection; 10 | import java.sql.ResultSet; 11 | import java.sql.SQLException; 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.Collection; 15 | import java.util.Collections; 16 | import java.util.HashSet; 17 | import java.util.List; 18 | import java.util.Set; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | /** 23 | * 24 | * 25 | * @author lenz 26 | */ 27 | public abstract class ObjectiveFetch { 28 | 29 | private final static Logger logger = LoggerFactory.getLogger(ObjectiveFetch.class); 30 | 31 | List deferredLoaders = new ArrayList(); 32 | 33 | /** 34 | * Defines a table. 35 | * You need this if you are going to use the query() menthod and do 36 | * reads and updates. 37 | * If you just need reading, you can skip it all and use queryDirect() 38 | * passing the fields yourself. 39 | * 40 | * @return 41 | */ 42 | 43 | public SqlTable table() { 44 | return null; 45 | } 46 | 47 | /** 48 | * Builds an element out of your row data. 49 | * The element is appened to the list of results, until you pass "null" 50 | * to signify you don't want it appended (this can be useful e.g. if 51 | * you are loading objects with sub-objects as one single join). 52 | * 53 | * @param rs 54 | * @return The list of objects. 55 | * @throws SQLException 56 | */ 57 | 58 | public abstract T load(ResultSet rs) throws SQLException; 59 | 60 | /** 61 | * Saves an object to the database. 62 | * This method is not abstract as you may well have objects that 63 | * you never want to save - e.g. results of aggregation queries. 64 | * 65 | * @param obj The object to be saved 66 | * @param su The field-set of the table, where yuoi will bind data. 67 | * @throws SQLException 68 | */ 69 | 70 | public void save(T obj, FieldSet su) throws SQLException { 71 | throw new IllegalArgumentException("Method save() undefined for " + obj.getClass()); 72 | } 73 | 74 | /** 75 | * Saves sub-objects, if that's needed. 76 | * 77 | * @param conn 78 | * @param obj my main object 79 | * @throws SQLException 80 | */ 81 | 82 | public void saveSubObjects( Connection conn, T obj ) throws SQLException { 83 | 84 | } 85 | 86 | /** 87 | * After an insert, this call-back is hit to updfate your object with the 88 | * new PK value. 89 | * You choose wheter you want o use this or not. 90 | * 91 | * @param pkFromDb The PK assigned by your DB. 92 | */ 93 | 94 | public void updatePrimaryKey( T object, String pkFromDb ) { 95 | // Override me 96 | logger.debug( "PK read from DB is {} but I'm not storing it", pkFromDb ); 97 | } 98 | 99 | /** 100 | * Adds a deferredLoader. 101 | * 102 | * @param l 103 | */ 104 | 105 | public void deferLoading( DeferredLoader l ) { 106 | deferredLoaders.add(l); 107 | } 108 | 109 | 110 | /** 111 | * Returns the result of a query. 112 | * In this case you have to pass a query like 113 | * SELECT ... FROM ... JOIN ... WHERE .... 114 | * 115 | * In general you will want to use the query() method 116 | * that takes care of most things for you. 117 | * 118 | * @see query() 119 | * @param conn 120 | * @param sql 121 | * @return 122 | * @throws SQLException 123 | */ 124 | 125 | public List queryDirect(Connection conn, final String sql) throws SQLException { 126 | 127 | final List results = new ArrayList(); 128 | deferredLoaders.clear(); 129 | 130 | new JdbcPattern() { 131 | 132 | @Override 133 | public void performJdbcAccess(Connection conn) throws SQLException { 134 | stmt = conn.createStatement(); 135 | 136 | // Execute the run 137 | rs = stmt.executeQuery(sql); 138 | 139 | // Loop through the result set 140 | while (rs.next()) { 141 | T result = load(rs); 142 | if ( result != null ) { 143 | results.add( result ); 144 | } 145 | } 146 | } 147 | }.run(conn); 148 | 149 | for (DeferredLoader dl: deferredLoaders ) { 150 | dl.query(conn); 151 | } 152 | 153 | // I want to make sure we keep no pointers to the object we just created. 154 | deferredLoaders.clear(); 155 | return results; 156 | } 157 | 158 | /** 159 | * Runs a query for this object by passing the WHERE ... ORDER BY ... clauses. 160 | * 161 | * 162 | * @param conn 163 | * @param sqlWhere 164 | * @return 165 | * @throws SQLException 166 | */ 167 | 168 | public List query(Connection conn, final String sqlWhere) throws SQLException { 169 | 170 | SqlTable table = table(); 171 | 172 | if ( table == null ) { 173 | throw new SQLException("Table appears to be undefined. You should use the queryDirect() method."); 174 | } 175 | 176 | StringBuilder sbFields = new StringBuilder(); 177 | sbFields.append( table.getPk().name ); 178 | 179 | for ( SqlField f: table.values() ) { 180 | sbFields.append(", ").append( f.name ); 181 | } 182 | 183 | String joins = ""; 184 | String tables = table.name; 185 | 186 | StringBuilder sb = new StringBuilder(); 187 | 188 | sb.append( "SELECT ").append( sbFields ).append( "\n" ) 189 | .append( " FROM ").append( tables ).append( "\n"); 190 | 191 | if (joins.length() > 0 ) { 192 | sb.append( joins ).append(" \n "); 193 | } 194 | 195 | sb.append( sqlWhere ); 196 | 197 | return queryDirect(conn, sb.toString() ); 198 | } 199 | 200 | /** 201 | * Loads one specific object by primary key. 202 | * If no object is found (or moe than one, but that's unlikely), raises an exception. 203 | * 204 | * @param conn 205 | * @param usedAs 206 | * @return 207 | * @throws SQLException 208 | */ 209 | public T get( Connection conn, int pk ) throws SQLException { 210 | List results = getAll( conn, Arrays.asList( new String[]{ "" + pk } )); 211 | return results.get(0); 212 | } 213 | 214 | 215 | /** 216 | * Get all items by PK. 217 | * Mke sure that we get the _same_ number of results we have in our set of 218 | * unique inputs. 219 | * 220 | * @param conn 221 | * @param pks 222 | * @return 223 | * @throws SQLException 224 | */ 225 | 226 | 227 | public List getAll( Connection conn, List pks ) throws SQLException { 228 | 229 | if ( pks.isEmpty() ) { 230 | return Collections.EMPTY_LIST; 231 | } 232 | 233 | Set sKeys = new HashSet( pks ); 234 | 235 | SqlField myPkField = table().getPk(); 236 | 237 | StringBuilder sb = new StringBuilder(); 238 | sb.append( " WHERE ").append( myPkField.name ).append( " IN ( "); 239 | SqlTools.addListToStringBuilder(sb, sKeys, ", ", "-1") ; 240 | sb.append( " ) "); 241 | 242 | List results = query( conn, sb.toString() ); 243 | 244 | if ( results.size() != sKeys.size() ) { 245 | throw new SQLException("Expected " + sKeys.size() + " results but only got " + results.size() + " when searching by PK: " + sb.toString() ); 246 | } 247 | 248 | return results; 249 | 250 | } 251 | 252 | /** 253 | * 254 | * @param conn 255 | * @param object 256 | * @param parentPk 257 | * @throws SQLException 258 | */ 259 | 260 | public void commit( Connection conn, T object, String parentPk ) throws SQLException { 261 | 262 | FieldSet fs = new FieldSet(table()); 263 | save(object, fs); 264 | 265 | if ( !parentPk.isEmpty() ) { 266 | SqlField parentPkF = table().getParentPk(); 267 | if ( parentPkF == null ) { 268 | throw new SQLException( "The table does not have a parentPk field"); 269 | } else { 270 | fs.set(parentPkF.name, new StringValue(parentPk) ); 271 | } 272 | } 273 | 274 | SqlUpdater su = new SqlUpdater(fs); 275 | 276 | if (su.isInsert()) { 277 | JdbcPattern pIns = JdbcPattern.insert(conn, su.getInsertQuery()); 278 | updatePrimaryKey( object, pIns.insertKey ); 279 | 280 | } else { 281 | JdbcPattern.update(conn, su.getUpdateQuery()); 282 | } 283 | 284 | // if we have any sub objects, let's save them. 285 | saveSubObjects(conn, object); 286 | 287 | } 288 | 289 | 290 | 291 | /** 292 | * Saves an object to the database. 293 | * 294 | * @param conn 295 | * @param object 296 | * @throws SQLException 297 | */ 298 | 299 | public void commit(Connection conn, T object) throws SQLException { 300 | commit( conn, object, ""); 301 | } 302 | 303 | /** 304 | * Commits a set of objects having the same parentPk. 305 | * 306 | * @param conn 307 | * @param objects 308 | * @param parentPk 309 | * @throws SQLException 310 | */ 311 | 312 | public void commitAll( Connection conn, Collection objects, String parentPk ) throws SQLException { 313 | for (T o: objects) { 314 | commit( conn, o, parentPk); 315 | } 316 | } 317 | 318 | /** 319 | * Commit a set of objects. 320 | * 321 | * @param conn 322 | * @param objects 323 | * @throws SQLException 324 | */ 325 | 326 | public void commitAll( Connection conn, Collection objects ) throws SQLException { 327 | commitAll( conn, objects, ""); 328 | } 329 | 330 | 331 | } 332 | -------------------------------------------------------------------------------- /classes/ch/loway/oss/ObjectiveSync/SqlTools.java: -------------------------------------------------------------------------------- 1 | package ch.loway.oss.ObjectiveSync; 2 | 3 | import ch.loway.oss.ObjectiveSync.table.SqlTable; 4 | import java.sql.Connection; 5 | import java.sql.DriverManager; 6 | import java.sql.ResultSet; 7 | import java.sql.SQLException; 8 | import java.text.SimpleDateFormat; 9 | import java.util.ArrayList; 10 | import java.util.Collection; 11 | import java.util.Date; 12 | import java.util.List; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | /** 17 | * 18 | * 19 | * @author lenz 20 | */ 21 | public class SqlTools { 22 | 23 | public static final Logger logger = LoggerFactory.getLogger(SqlTools.class); 24 | 25 | /** 26 | * Opens a connection. 27 | * 28 | * @param connString 29 | * @return a working connection. 30 | * @throws SQLException 31 | */ 32 | public static Connection openConnection(String connString) throws SQLException { 33 | Connection conn = DriverManager.getConnection( connString ); 34 | return conn; 35 | } 36 | 37 | /** 38 | * Closes the connection. No matter what. 39 | * 40 | * @param conn 41 | */ 42 | public static void close(Connection conn) { 43 | if (conn != null) { 44 | try { 45 | conn.close(); 46 | } catch (SQLException e) { 47 | logger.error("Error closing connection", e); 48 | } 49 | } 50 | } 51 | 52 | public static void execSql( Connection conn, String sql ) throws SQLException { 53 | 54 | ObjectiveFetch of = new ObjectiveFetch() { 55 | 56 | @Override 57 | public SqlTable table() { 58 | return null; 59 | } 60 | 61 | @Override 62 | public Object load(ResultSet rs) throws SQLException { 63 | return null; 64 | } 65 | }; 66 | 67 | of.query(conn, sql); 68 | } 69 | 70 | 71 | /** 72 | * Convert any string to a number. If invalid, returns zero. 73 | * 74 | * @param s 75 | * @return the number; if empty or invalid, zero. 76 | */ 77 | public static int cint(String s) { 78 | try { 79 | return Integer.parseInt(s); 80 | } catch (NumberFormatException e) { 81 | return 0; 82 | } 83 | } 84 | 85 | public static String asDate(Date d) { 86 | return gtData(SQL_DATE, d); 87 | 88 | } 89 | 90 | public static String asDateTime(Date d) { 91 | return gtData(SQL_DATETIME, d); 92 | } 93 | 94 | /** 95 | * Formatta una data. 96 | * Essitono alcune costanti nell'oggetto U definite per i casi pi? comuni 97 | * Se la data passata ? "null", ritorna la stringa vuota. 98 | * 99 | * @param stFormato Vedi le costanti 100 | * @param dtData Oggetto Date valido 101 | * @return Stringa formattata 102 | */ 103 | public static String gtData(String stFormato, Date dtData) { 104 | 105 | if (dtData == null) { 106 | return ""; 107 | } 108 | 109 | SimpleDateFormat formatter = new SimpleDateFormat(stFormato); 110 | return formatter.format(dtData); 111 | } 112 | public static final String SQL_DATE = "yyyy-MM-dd"; 113 | public static final String SQL_DATETIME = "yyyy-MM-dd HH:mm:ss"; 114 | 115 | public static String qq(String s) { 116 | StringBuilder sb = new StringBuilder(s.length() + 5); 117 | return pvtQuoteString(sb, s, " '", "' "); 118 | } 119 | 120 | public static String quoteFieldName(String s) { 121 | StringBuilder sb = new StringBuilder(s.length() + 5); 122 | return pvtQuoteString(sb, s, " `", "` "); 123 | } 124 | 125 | /** 126 | * Implementation of SQL quoting with an optional start and end (usually 127 | * the " sign plus relevant spaces). 128 | * 129 | * 130 | * @param sb builder 131 | * @param s value 132 | * @param prefix the prefix 133 | * @param postfix the postfix 134 | * @return a quoted string 135 | */ 136 | 137 | private static String pvtQuoteString(StringBuilder sb, String s, String prefix, String postfix) { 138 | 139 | if (s == null) { 140 | s = ""; 141 | } 142 | 143 | sb.append(prefix); 144 | 145 | char c; 146 | for (int i = 0; i < s.length(); i++) { 147 | c = s.charAt(i); 148 | 149 | // trasforma 150 | // ' -> \' 151 | // \n -> {nullo} 152 | // \r -> {nullo} 153 | // \ -> \\ 154 | if (c == '\'') { 155 | sb.append("\\'"); 156 | } else if (c == '\n') { 157 | sb.append("\\n"); 158 | } else if (c == '\r') { 159 | sb.append("\\r"); 160 | } else if (c == '\\') { 161 | sb.append("\\\\"); 162 | } else { 163 | sb.append(c); 164 | } 165 | } 166 | 167 | sb.append(postfix); 168 | return sb.toString(); 169 | } 170 | 171 | public static void addListToStringBuilder(StringBuilder sb, Collection lS, String separator, String emptyCase) { 172 | 173 | if (lS.isEmpty()) { 174 | sb.append(emptyCase); 175 | 176 | } else { 177 | 178 | int pos = 0; 179 | for (String val : lS) { 180 | if (pos > 0) { 181 | sb.append(separator); 182 | } 183 | sb.append(val); 184 | pos++; 185 | } 186 | } 187 | 188 | } 189 | 190 | /** 191 | * Gets us an IN( ... ) clause backed by enums. 192 | * 193 | * @param clauses 194 | * @param emptyList 195 | * @return 196 | */ 197 | 198 | public static String quoteInEnum( List clauses, String emptyList ) { 199 | 200 | List vals = new ArrayList(); 201 | for ( Enum e: clauses ) { 202 | vals.add(e.name()); 203 | } 204 | 205 | return quoteInClause(vals, emptyList); 206 | 207 | } 208 | 209 | /** 210 | * An IN( ... ) clause backed by a List of Strings. 211 | * 212 | * @param clauses 213 | * @param emptyList 214 | * @return 215 | */ 216 | 217 | public static String quoteInClause( List clauses, String emptyList ) { 218 | StringBuilder sb = new StringBuilder(); 219 | SqlTools.addListToStringBuilder(sb, quoteList(clauses, emptyList), ", ", ""); 220 | return sb.toString(); 221 | } 222 | 223 | /** 224 | * Quotes a list of items and separates them (usually with a comma or similar). 225 | * 226 | * @param values 227 | * @param emptyCase 228 | * @return 229 | */ 230 | 231 | public static List quoteList( List values, String emptyCase ) { 232 | List out = new ArrayList( values.size() ); 233 | 234 | if ( values.isEmpty() ) { 235 | out.add( SqlTools.qq(emptyCase) ); 236 | } else { 237 | for ( String s: values ) { 238 | out.add( SqlTools.qq(s) ); 239 | } 240 | } 241 | return out; 242 | } 243 | 244 | 245 | } 246 | -------------------------------------------------------------------------------- /classes/ch/loway/oss/ObjectiveSync/table/SqlField.java: -------------------------------------------------------------------------------- 1 | package ch.loway.oss.ObjectiveSync.table; 2 | 3 | import ch.loway.oss.ObjectiveSync.table.type.FuncValue; 4 | import ch.loway.oss.ObjectiveSync.table.type.TableValue; 5 | 6 | /** 7 | * This immutable object describes a table field. 8 | * 9 | * @author lenz 10 | */ 11 | public class SqlField { 12 | 13 | public final String name; 14 | public final String type; 15 | public final UsedAs usedAs; 16 | public final TableValue defaultInsert; 17 | public final TableValue defaultUpdate; 18 | 19 | /** 20 | * Creator. 21 | * 22 | * @param name 23 | * @param type 24 | * @param usedAs 25 | * @param defaultInsert 26 | * @param defaultUpdate 27 | */ 28 | 29 | public SqlField(String name, String type, UsedAs usedAs, TableValue defaultInsert, TableValue defaultUpdate) { 30 | this.name = name; 31 | this.type = type; 32 | this.usedAs = usedAs; 33 | this.defaultInsert = defaultInsert; 34 | this.defaultUpdate = defaultUpdate; 35 | } 36 | 37 | /** 38 | * Creates a PK field. 39 | * 40 | * @param name 41 | * @param def 42 | * @param funcDefInsert 43 | * @return a PK field. 44 | */ 45 | public static SqlField pk(String name, String def, String funcDefInsert) { 46 | return new SqlField(name, def, UsedAs.PK, new FuncValue(funcDefInsert), null); 47 | } 48 | 49 | public static SqlField parentPk(String name, String def, String funcDefInsert) { 50 | return new SqlField(name, def, UsedAs.PARENT_PK, new FuncValue(funcDefInsert), null); 51 | } 52 | 53 | public static SqlField str(String name, String def, TableValue defInsert, TableValue defUpdate) { 54 | return new SqlField(name, def, UsedAs.PLAIN, defInsert, defUpdate); 55 | } 56 | 57 | public static SqlField i(String name, String def, TableValue defInsert, TableValue defUpdate) { 58 | return new SqlField(name, def, UsedAs.PLAIN, defInsert, defUpdate); 59 | } 60 | 61 | 62 | 63 | /** 64 | * Does this field have any special reason to be here? 65 | */ 66 | 67 | public static enum UsedAs { 68 | PLAIN, 69 | PK, 70 | PARENT_PK 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /classes/ch/loway/oss/ObjectiveSync/table/SqlTable.java: -------------------------------------------------------------------------------- 1 | 2 | package ch.loway.oss.ObjectiveSync.table; 3 | 4 | import java.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | /** 9 | * Describes a table as a set of fields (columns). 10 | * 11 | * @author lenz 12 | */ 13 | public class SqlTable { 14 | 15 | public final String name; 16 | List fields = new ArrayList(); 17 | 18 | public SqlTable( String tableName ) { 19 | name = tableName; 20 | } 21 | 22 | public SqlTable field( SqlField f ) { 23 | fields.add( f ); 24 | return this; 25 | } 26 | 27 | /** 28 | * Gets us the PK field. 29 | * 30 | * @return the PK in use 31 | */ 32 | 33 | public SqlField getPk() { 34 | return getFieldUsedFor(SqlField.UsedAs.PK); 35 | 36 | } 37 | 38 | /** 39 | * Get the Parents' PK field 40 | * @return 41 | */ 42 | 43 | public SqlField getParentPk() { 44 | return getFieldUsedFor(SqlField.UsedAs.PARENT_PK); 45 | } 46 | 47 | /** 48 | * Finds a field. 49 | * 50 | * @param usage 51 | * @return 52 | */ 53 | 54 | private SqlField getFieldUsedFor( SqlField.UsedAs usage ) { 55 | for ( SqlField f: fields ) { 56 | if ( f.usedAs == usage ) { 57 | return f; 58 | } 59 | } 60 | return null; 61 | } 62 | 63 | 64 | /** 65 | * Is a field defined for this table? 66 | * 67 | * @param name 68 | * @return true if the field is currently defined. 69 | */ 70 | 71 | public boolean fieldExists( String name ) { 72 | for ( SqlField f: fields ) { 73 | if ( f.name.equals(name) ) { 74 | return true; 75 | } 76 | } 77 | return false; 78 | } 79 | 80 | /** 81 | * A collection of columns describing the table. 82 | * 83 | * @return the columns 84 | */ 85 | 86 | public List values() { 87 | return Collections.unmodifiableList(fields); 88 | } 89 | 90 | } 91 | 92 | -------------------------------------------------------------------------------- /classes/ch/loway/oss/ObjectiveSync/table/type/FuncValue.java: -------------------------------------------------------------------------------- 1 | 2 | package ch.loway.oss.ObjectiveSync.table.type; 3 | 4 | /** 5 | * 6 | * 7 | * $Id$ 8 | * @author lenz 9 | */ 10 | public class FuncValue extends TableValue { 11 | 12 | private final String func; 13 | 14 | @Override 15 | public String embeddableValue() { 16 | return func; 17 | } 18 | 19 | public FuncValue(String func) { 20 | this.func = func; 21 | } 22 | } 23 | 24 | // $Log$ 25 | // 26 | -------------------------------------------------------------------------------- /classes/ch/loway/oss/ObjectiveSync/table/type/IntValue.java: -------------------------------------------------------------------------------- 1 | 2 | package ch.loway.oss.ObjectiveSync.table.type; 3 | 4 | /** 5 | * 6 | * 7 | * $Id$ 8 | * @author lenz 9 | */ 10 | public class IntValue extends TableValue { 11 | private final int val; 12 | 13 | @Override 14 | public String embeddableValue() { 15 | return Integer.toString(val); 16 | } 17 | 18 | public IntValue(int val) { 19 | this.val = val; 20 | } 21 | } 22 | 23 | // $Log$ 24 | // 25 | -------------------------------------------------------------------------------- /classes/ch/loway/oss/ObjectiveSync/table/type/StringValue.java: -------------------------------------------------------------------------------- 1 | 2 | package ch.loway.oss.ObjectiveSync.table.type; 3 | 4 | import ch.loway.oss.ObjectiveSync.SqlTools; 5 | 6 | /** 7 | * 8 | * 9 | * $Id$ 10 | * @author lenz 11 | */ 12 | public class StringValue extends TableValue { 13 | 14 | private final String val; 15 | 16 | @Override 17 | public String embeddableValue() { 18 | return SqlTools.qq(val); 19 | } 20 | 21 | public StringValue(String val) { 22 | this.val = val; 23 | } 24 | 25 | 26 | 27 | 28 | 29 | } 30 | 31 | // $Log$ 32 | // 33 | -------------------------------------------------------------------------------- /classes/ch/loway/oss/ObjectiveSync/table/type/TableValue.java: -------------------------------------------------------------------------------- 1 | 2 | package ch.loway.oss.ObjectiveSync.table.type; 3 | 4 | /** 5 | * 6 | * 7 | * $Id$ 8 | * @author lenz 9 | */ 10 | public abstract class TableValue { 11 | 12 | public abstract String embeddableValue(); 13 | 14 | } 15 | 16 | // $Log$ 17 | // 18 | -------------------------------------------------------------------------------- /classes/ch/loway/oss/ObjectiveSync/updater/FieldSet.java: -------------------------------------------------------------------------------- 1 | package ch.loway.oss.ObjectiveSync.updater; 2 | 3 | import ch.loway.oss.ObjectiveSync.table.SqlField; 4 | import ch.loway.oss.ObjectiveSync.table.SqlTable; 5 | import ch.loway.oss.ObjectiveSync.table.type.TableValue; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * A set of fields, ready to be written to the DB: 11 | * 12 | * @author lenz 13 | */ 14 | public class FieldSet { 15 | 16 | SqlTable table; 17 | Map defFields = new HashMap(); 18 | 19 | public FieldSet(SqlTable referenceTable) { 20 | table = referenceTable; 21 | } 22 | 23 | public FieldSet set(String name, TableValue t) { 24 | if (table.fieldExists(name)) { 25 | defFields.put(name, t); 26 | } else { 27 | throw new IllegalArgumentException("Undefined field: '" + name + "' in table "); 28 | } 29 | return this; 30 | } 31 | 32 | /** 33 | * Did we set a PK for this transaction? 34 | * 35 | * @return true if a PK is defined; false if none (insert). 36 | */ 37 | public boolean isPkSet() { 38 | SqlField pk = table.getPk(); 39 | return (defFields.containsKey(pk.name)); 40 | } 41 | 42 | /** 43 | * What is the PK field to be used? 44 | * 45 | * @return the PK field 46 | */ 47 | public Map getPkValue() { 48 | Map hmOut = new HashMap(); 49 | 50 | SqlField pk = table.getPk(); 51 | TableValue tv = defFields.get(pk.name); 52 | String quotedVal = ""; 53 | 54 | if (tv == null) { 55 | quotedVal = pk.defaultInsert.embeddableValue(); 56 | } else { 57 | quotedVal = tv.embeddableValue(); 58 | } 59 | 60 | hmOut.put(pk.name, quotedVal); 61 | return hmOut; 62 | } 63 | 64 | /** 65 | * Gets all values but the PK. 66 | * 67 | * @return a map of all fields of this table. 68 | */ 69 | public Map getValuesButPk() { 70 | 71 | Map hmOut = new HashMap(); 72 | for (SqlField f : table.values()) { 73 | if ( f.usedAs != SqlField.UsedAs.PK) { 74 | 75 | if (defFields.containsKey(f.name)) { 76 | 77 | TableValue val = defFields.get(f.name); 78 | hmOut.put(f.name, val.embeddableValue()); 79 | 80 | } else { 81 | 82 | TableValue tv = null; 83 | if (isPkSet()) { 84 | tv = f.defaultUpdate; 85 | } else { 86 | tv = f.defaultInsert; 87 | } 88 | 89 | // se non ho un field e questo non ha default, proprio lo ignoro 90 | if (tv != null) { 91 | hmOut.put(f.name, tv.embeddableValue()); 92 | } 93 | } 94 | } 95 | } 96 | 97 | return hmOut; 98 | 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /classes/ch/loway/oss/ObjectiveSync/updater/SqlFieldVal.java: -------------------------------------------------------------------------------- 1 | package ch.loway.oss.ObjectiveSync.updater; 2 | 3 | public class SqlFieldVal { 4 | 5 | // SqlField field; 6 | // TableValue currentValue; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /classes/ch/loway/oss/ObjectiveSync/updater/SqlUpdater.java: -------------------------------------------------------------------------------- 1 | 2 | package ch.loway.oss.ObjectiveSync.updater; 3 | 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Map.Entry; 8 | 9 | /** 10 | * Builds insert and update queries. 11 | * 12 | * @author lenz 13 | */ 14 | public class SqlUpdater { 15 | 16 | private final FieldSet fs; 17 | 18 | public SqlUpdater(FieldSet fs) { 19 | this.fs = fs; 20 | } 21 | 22 | /** 23 | * Builds an INSERT query for the object. 24 | * The PK is assumed to be auto-generated. 25 | * 26 | * @return SQL query as a string 27 | */ 28 | 29 | public String getInsertQuery() { 30 | 31 | if ( fs.isPkSet() ) { 32 | throw new IllegalStateException("This is not an INSERT query"); 33 | } 34 | 35 | String tableName = fs.table.name; 36 | Map mVals = fs.getValuesButPk(); 37 | 38 | List fields = new ArrayList(mVals.keySet()); 39 | List vals = new ArrayList(); 40 | for ( String fieldName: fields ) { 41 | vals.add( mVals.get(fieldName) ); 42 | } 43 | 44 | StringBuilder sb = new StringBuilder(); 45 | sb.append( "INSERT INTO " ).append( tableName ).append( " ( "); 46 | 47 | for ( int i=0; i < fields.size(); i++ ) { 48 | sb.append( (i>0) ? ", " : "" ); 49 | sb.append( fields.get(i) ); 50 | } 51 | 52 | sb.append( " ) VALUES ( "); 53 | 54 | for ( int i=0; i < vals.size(); i++ ) { 55 | sb.append( (i>0) ? ", " : "" ); 56 | sb.append( vals.get(i) ); 57 | } 58 | 59 | sb.append( ") "); 60 | return sb.toString(); 61 | } 62 | 63 | /** 64 | * Builds an update query. 65 | * 66 | * We expect this to be by PK. 67 | * \todo handling Optilock 68 | * 69 | * @return SQL query as a string 70 | */ 71 | 72 | public String getUpdateQuery() { 73 | 74 | if ( !fs.isPkSet() ) { 75 | throw new IllegalStateException("This is not an UPDATE query"); 76 | } 77 | 78 | String tableName = fs.table.name; 79 | Map mVals = fs.getValuesButPk(); 80 | Map mPk = fs.getPkValue(); 81 | 82 | StringBuilder sb = new StringBuilder(); 83 | sb.append("UPDATE ").append(tableName).append(" SET "); 84 | 85 | int i = 0; 86 | for ( Entry e: mVals.entrySet()) { 87 | 88 | sb.append( (i>0) ? ", " : "" ); 89 | sb.append( e.getKey() ).append( "=" ).append( e.getValue() ); 90 | i++; 91 | } 92 | 93 | sb.append(" WHERE "); 94 | for ( Entry e: mPk.entrySet()) { 95 | 96 | sb.append( e.getKey() ).append( " = " ).append( e.getValue() ); 97 | 98 | } 99 | 100 | return sb.toString(); 101 | } 102 | 103 | /** 104 | * Checks if we have a defined PK (so we UPDATE) the object 105 | * or we do not have it and so we INSERT. 106 | * 107 | * @return whether the PK is defined 108 | */ 109 | 110 | public boolean isInsert() { 111 | return !fs.isPkSet(); 112 | } 113 | 114 | } 115 | 116 | -------------------------------------------------------------------------------- /classes/ch/loway/oss/ObjectiveSync/updater/deferred/DeferredLoadByParent.java: -------------------------------------------------------------------------------- 1 | 2 | package ch.loway.oss.ObjectiveSync.updater.deferred; 3 | 4 | import ch.loway.oss.ObjectiveSync.updater.deferred.DeferredLoader; 5 | import ch.loway.oss.ObjectiveSync.ObjectiveFetch; 6 | 7 | /** 8 | * 9 | * 10 | * @author lenz 11 | */ 12 | public abstract class DeferredLoadByParent extends DeferredLoader { 13 | 14 | String whereClause = ""; 15 | 16 | public DeferredLoadByParent setup( ObjectiveFetch fetcher, int pkId ) { 17 | this.of = fetcher; 18 | 19 | String field = fetcher.table().getParentPk().name; 20 | whereClause = "WHERE " + field + " = " + pkId; 21 | return this; 22 | } 23 | 24 | @Override 25 | public String getWhereClause() { 26 | return whereClause; 27 | } 28 | 29 | 30 | } 31 | 32 | // $Log$ 33 | // 34 | -------------------------------------------------------------------------------- /classes/ch/loway/oss/ObjectiveSync/updater/deferred/DeferredLoader.java: -------------------------------------------------------------------------------- 1 | 2 | package ch.loway.oss.ObjectiveSync.updater.deferred; 3 | 4 | import ch.loway.oss.ObjectiveSync.ObjectiveFetch; 5 | import java.sql.Connection; 6 | import java.sql.SQLException; 7 | import java.util.HashSet; 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | /** 12 | * Does deferred loading. 13 | * 14 | * @author lenz 15 | */ 16 | public abstract class DeferredLoader { 17 | 18 | public ObjectiveFetch of = null; 19 | 20 | public abstract void update( Set sons ); 21 | 22 | 23 | public void query( Connection conn ) throws SQLException { 24 | List results = of.query(conn, getWhereClause() ); 25 | Set sResults = new HashSet( results ); 26 | update( sResults ); 27 | } 28 | 29 | public abstract String getWhereClause(); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /tests/ch/loway/oss/ObjectiveSync/CSTest.java: -------------------------------------------------------------------------------- 1 | 2 | package ch.loway.oss.ObjectiveSync; 3 | 4 | import org.junit.After; 5 | import org.junit.AfterClass; 6 | import org.junit.Before; 7 | import org.junit.BeforeClass; 8 | import org.junit.Test; 9 | import static org.junit.Assert.*; 10 | 11 | /** 12 | * 13 | * @author lenz 14 | */ 15 | public class CSTest { 16 | 17 | public CSTest() { 18 | } 19 | 20 | @BeforeClass 21 | public static void setUpClass() throws Exception { 22 | } 23 | 24 | @AfterClass 25 | public static void tearDownClass() throws Exception { 26 | } 27 | 28 | @Before 29 | public void setUp() { 30 | } 31 | 32 | @After 33 | public void tearDown() { 34 | } 35 | 36 | @Test 37 | public void testSomeMethod() { 38 | assertTrue("Have version", CS.VERSION.length() > 0 ); 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /tests/ch/loway/oss/ObjectiveSync/JdbcPatternTest.java: -------------------------------------------------------------------------------- 1 | 2 | package ch.loway.oss.ObjectiveSync; 3 | 4 | import java.sql.Connection; 5 | import java.sql.SQLException; 6 | import java.sql.Statement; 7 | import org.junit.After; 8 | import org.junit.AfterClass; 9 | import org.junit.Before; 10 | import org.junit.BeforeClass; 11 | import org.junit.Test; 12 | import static org.junit.Assert.*; 13 | 14 | /** 15 | * Now testing the JdbcPattern object 16 | * 17 | * @author lenz 18 | */ 19 | public class JdbcPatternTest { 20 | 21 | public JdbcPatternTest() { 22 | } 23 | 24 | Connection conn = null; 25 | 26 | @BeforeClass 27 | public static void setUpClass() throws ClassNotFoundException { 28 | Class.forName("org.h2.Driver"); 29 | } 30 | 31 | @AfterClass 32 | public static void tearDownClass() { 33 | } 34 | 35 | @Before 36 | public void setUp() throws SQLException { 37 | conn = SqlTools.openConnection("jdbc:h2:mem:test"); 38 | String sql = "CREATE TABLE EXAMPLE ( id int auto_increment, name char(50), surname char(50) )"; 39 | 40 | Statement stmt = conn.createStatement(); 41 | stmt.execute(sql); 42 | 43 | } 44 | 45 | @After 46 | public void tearDown() throws SQLException { 47 | conn.close(); 48 | } 49 | 50 | /** 51 | * Test inserting into the DB. 52 | */ 53 | @Test 54 | public void testInsert() throws Exception { 55 | 56 | JdbcPattern pInsert = JdbcPattern.insert(conn, "INSERT INTO EXAMPLE (name, surname) VALUES ('A', 'B')" ); 57 | 58 | System.out.println( "Gen key:" + pInsert.insertKey ); 59 | assertTrue( "Chiave generata", pInsert.insertKey.length() > 0); 60 | } 61 | 62 | @Test 63 | public void testUpdate() throws Exception { 64 | JdbcPattern pInsert = JdbcPattern.insert(conn, "INSERT INTO EXAMPLE (name, surname) VALUES ('A', 'B')" ); 65 | 66 | JdbcPattern.update(conn, "UPDATE EXAMPLE SET surname ='X' WHERE id=" + pInsert.insertKey ); 67 | } 68 | 69 | @Test 70 | public void testExec() throws Exception { 71 | JdbcPattern pExec = JdbcPattern.exec(conn, 72 | "CREATE TABLE EXAMPLE2 ( id int auto_increment, name char(50), surname char(50) )" ); 73 | 74 | 75 | } 76 | 77 | 78 | } 79 | -------------------------------------------------------------------------------- /tests/ch/loway/oss/ObjectiveSync/ObjectiveFetchTest.java: -------------------------------------------------------------------------------- 1 | 2 | package ch.loway.oss.ObjectiveSync; 3 | 4 | import java.util.ArrayList; 5 | import java.util.Set; 6 | import java.util.List; 7 | import ch.loway.oss.ObjectiveSync.maps.Person; 8 | import ch.loway.oss.ObjectiveSync.maps.PersonDB; 9 | import java.sql.Connection; 10 | import java.sql.SQLException; 11 | import java.util.HashSet; 12 | import org.junit.After; 13 | import org.junit.AfterClass; 14 | import org.junit.Before; 15 | import org.junit.BeforeClass; 16 | import org.junit.Test; 17 | import static org.junit.Assert.*; 18 | 19 | /** 20 | * 21 | * @author lenz 22 | */ 23 | public class ObjectiveFetchTest { 24 | 25 | Connection conn = null; 26 | 27 | public ObjectiveFetchTest() { 28 | } 29 | 30 | @BeforeClass 31 | public static void setUpClass() throws Exception { 32 | } 33 | 34 | @AfterClass 35 | public static void tearDownClass() throws Exception { 36 | } 37 | 38 | @Before 39 | public void setUp() throws ClassNotFoundException, SQLException { 40 | Class.forName("org.h2.Driver"); 41 | conn = SqlTools.openConnection("jdbc:h2:mem:test"); 42 | 43 | String sql = "CREATE TABLE person ( id int auto_increment, name char(50), surname char(50), org_id int )"; 44 | JdbcPattern.exec(conn, sql); 45 | 46 | } 47 | 48 | @After 49 | public void tearDown() { 50 | SqlTools.close(conn); 51 | conn = null; 52 | } 53 | 54 | @Test 55 | public void testPlain() throws SQLException { 56 | 57 | PersonDB of = new PersonDB(); 58 | 59 | Person p = Person.build(0, "ike", "boo"); 60 | of.commit(conn, p); 61 | 62 | List lP = of.queryDirect(conn, "SELECT * FROM person"); 63 | assertEquals( "N persons", 1, lP.size()); 64 | 65 | } 66 | 67 | @Test 68 | public void testSetPKonInsert() throws SQLException { 69 | 70 | PersonDB of = new PersonDB(); 71 | 72 | Person p = Person.build(0, "ike", "boo"); 73 | of.commit(conn, p); 74 | 75 | assertTrue( "PK should be set", (p.id != 0) ); 76 | 77 | 78 | } 79 | 80 | @Test 81 | public void testCommitAll() throws SQLException { 82 | 83 | PersonDB of = new PersonDB(); 84 | 85 | Set collection = new HashSet(); 86 | 87 | collection.add( Person.build(0, "A", "B") ); 88 | collection.add( Person.build(0, "C", "D") ); 89 | collection.add( Person.build(0, "E", "F") ); 90 | 91 | of.commitAll(conn, collection); 92 | 93 | List lP = of.queryDirect(conn, "SELECT * FROM person"); 94 | assertEquals( "N persons", 3, lP.size()); 95 | } 96 | 97 | 98 | @Test 99 | public void testQuery_plain() throws SQLException { 100 | 101 | PersonDB of = new PersonDB(); 102 | 103 | Person p1 = Person.build(0, "jabba", "the hutt"); 104 | Person p2 = Person.build(0, "luke", "skywalker"); 105 | of.commit(conn, p1); 106 | of.commit(conn, p2); 107 | 108 | List lP = of.query(conn, "WHERE name = 'luke'"); 109 | assertEquals( "N persons", 1, lP.size()); 110 | assertEquals("Surname", "skywalker", lP.get(0).surname ); 111 | assertTrue( "ID set", lP.get(0).id != 0 ); 112 | 113 | } 114 | 115 | 116 | @Test 117 | public void testGetAll() throws SQLException { 118 | 119 | PersonDB of = new PersonDB(); 120 | 121 | List collection = new ArrayList(); 122 | collection.add( Person.build(0, "A", "B") ); 123 | collection.add( Person.build(0, "C", "D") ); 124 | collection.add( Person.build(0, "E", "F") ); 125 | of.commitAll(conn, collection); 126 | 127 | // Load all PKs 128 | List lQ = new ArrayList(); 129 | for ( Person p: collection) { 130 | lQ.add( "" + p.id ); 131 | } 132 | 133 | List lP = of.getAll( conn, lQ ); 134 | assertEquals( "N persons", 3, lP.size()); 135 | } 136 | 137 | @Test 138 | public void testGetAll_wrongKeyAdded() throws SQLException { 139 | 140 | PersonDB of = new PersonDB(); 141 | 142 | List collection = new ArrayList(); 143 | collection.add( Person.build(0, "A", "B") ); 144 | collection.add( Person.build(0, "C", "D") ); 145 | collection.add( Person.build(0, "E", "F") ); 146 | of.commitAll(conn, collection); 147 | 148 | // Load all PKs 149 | List lQ = new ArrayList(); 150 | for ( Person p: collection) { 151 | lQ.add( "" + p.id ); 152 | } 153 | lQ.add( "19137" ); 154 | 155 | try { 156 | List lP = of.getAll( conn, lQ ); 157 | } catch ( SQLException e) { 158 | System.out.println( e.toString() ); 159 | return; 160 | } 161 | fail( "No exception raised!"); 162 | 163 | 164 | } 165 | 166 | 167 | 168 | } -------------------------------------------------------------------------------- /tests/ch/loway/oss/ObjectiveSync/ObjectiveFetch_1N_Test.java: -------------------------------------------------------------------------------- 1 | 2 | package ch.loway.oss.ObjectiveSync; 3 | 4 | import ch.loway.oss.ObjectiveSync.JdbcPattern; 5 | import ch.loway.oss.ObjectiveSync.SqlTools; 6 | import java.util.ArrayList; 7 | import ch.loway.oss.ObjectiveSync.maps.Organization; 8 | import ch.loway.oss.ObjectiveSync.maps.OrganizationDB; 9 | import java.util.List; 10 | import ch.loway.oss.ObjectiveSync.maps.Person; 11 | import java.sql.Connection; 12 | import java.sql.SQLException; 13 | import org.junit.After; 14 | import org.junit.AfterClass; 15 | import org.junit.Before; 16 | import org.junit.BeforeClass; 17 | import org.junit.Test; 18 | import static org.junit.Assert.*; 19 | 20 | /** 21 | * tests ObjectiveFetch in 1:N associations. 22 | * 23 | * @author lenz 24 | */ 25 | public class ObjectiveFetch_1N_Test { 26 | 27 | Connection conn = null; 28 | 29 | public ObjectiveFetch_1N_Test() { 30 | } 31 | 32 | @BeforeClass 33 | public static void setUpClass() throws Exception { 34 | } 35 | 36 | @AfterClass 37 | public static void tearDownClass() throws Exception { 38 | } 39 | 40 | @Before 41 | public void setUp() throws ClassNotFoundException, SQLException { 42 | Class.forName("org.h2.Driver"); 43 | conn = SqlTools.openConnection("jdbc:h2:mem:test"); 44 | 45 | JdbcPattern.execAll(conn, new String[] { 46 | "CREATE TABLE person ( id int auto_increment, name char(50), surname char(50), org_id int )", 47 | "CREATE TABLE org ( id int auto_increment, name char(50) )" 48 | }); 49 | 50 | 51 | } 52 | 53 | @After 54 | public void tearDown() { 55 | SqlTools.close(conn); 56 | conn = null; 57 | } 58 | 59 | @Test 60 | public void testPlain() throws SQLException { 61 | 62 | OrganizationDB db = new OrganizationDB(); 63 | 64 | Organization org = Organization.build(0, "Evil empire"); 65 | org.addMember( Person.build(0, "Emperor", "Evil")); 66 | org.addMember( Person.build( 0, "Darth", "Vader")); 67 | 68 | db.commit(conn, org); 69 | 70 | List lP = db.query(conn, ""); 71 | assertEquals( "N orgs", 1, lP.size()); 72 | assertEquals( "People linked", 2, lP.get(0).members.size() ); 73 | } 74 | 75 | 76 | @Test 77 | public void testWrite() throws SQLException { 78 | 79 | OrganizationDB db = new OrganizationDB(); 80 | 81 | Organization org = Organization.build(0, "Evil empire"); 82 | org.addMember( Person.build(0, "Emperor", "Evil")); 83 | org.addMember( Person.build( 0, "Darth", "Vader")); 84 | 85 | db.commit(conn, org); 86 | 87 | // check that objects do have a PK (so we know they were saved). 88 | List members = new ArrayList( org.members ); 89 | assertTrue( "Org has PK", org.id != 0 ); 90 | assertTrue( "Org.m0 has PK", members.get(0).id != 0 ); 91 | assertTrue( "Org.m1 has PK", members.get(1).id != 0 ); 92 | 93 | } 94 | 95 | } -------------------------------------------------------------------------------- /tests/ch/loway/oss/ObjectiveSync/ObjectiveFetch_ReadOnlyTest.java: -------------------------------------------------------------------------------- 1 | 2 | package ch.loway.oss.ObjectiveSync; 3 | 4 | import ch.loway.oss.ObjectiveSync.maps.ACInfo; 5 | import ch.loway.oss.ObjectiveSync.maps.ACInfoDB; 6 | import java.util.List; 7 | import java.sql.Connection; 8 | import java.sql.SQLException; 9 | import org.junit.After; 10 | import org.junit.AfterClass; 11 | import org.junit.Before; 12 | import org.junit.BeforeClass; 13 | import org.junit.Test; 14 | import static org.junit.Assert.*; 15 | 16 | /** 17 | * Tests ObjectiveFetch in 1:N associations. 18 | * 19 | * @author lenz 20 | */ 21 | public class ObjectiveFetch_ReadOnlyTest { 22 | 23 | Connection conn = null; 24 | 25 | public ObjectiveFetch_ReadOnlyTest() { 26 | } 27 | 28 | @BeforeClass 29 | public static void setUpClass() throws Exception { 30 | } 31 | 32 | @AfterClass 33 | public static void tearDownClass() throws Exception { 34 | } 35 | 36 | @Before 37 | public void setUp() throws ClassNotFoundException, SQLException { 38 | Class.forName("org.h2.Driver"); 39 | conn = SqlTools.openConnection("jdbc:h2:mem:test"); 40 | 41 | JdbcPattern.execAll(conn, new String[] { 42 | "CREATE TABLE campaigns ( campaignId int, name char(50), pace char(50), securityKey char(50) )", 43 | "CREATE TABLE hopper ( campaign int, runMode char(50) )" 44 | }); 45 | 46 | 47 | } 48 | 49 | @After 50 | public void tearDown() { 51 | SqlTools.close(conn); 52 | conn = null; 53 | } 54 | 55 | @Test 56 | public void testPlainNotRunning() throws SQLException { 57 | 58 | JdbcPattern.execAll(conn, new String[] { 59 | "INSERT INTO campaigns ( campaignId, name, pace, securityKey) VALUES ( 10, 'Notused', 'RUNNABLE', 'xxx' ) " 60 | }); 61 | 62 | List lP = ACInfoDB.findAll(conn); 63 | assertEquals( "N info", 1, lP.size()); 64 | assertEquals( "#1: Name", "Notused", lP.get(0).name ); 65 | assertEquals( "#1: ID", 10, lP.get(0).campaignId ); 66 | assertEquals( "#1: SecKey", "xxx", lP.get(0).secKey ); 67 | assertEquals( "#1: Running", false, lP.get(0).isRunning ); 68 | } 69 | 70 | 71 | @Test 72 | public void testPlainExistsNotRunning() throws SQLException { 73 | 74 | JdbcPattern.execAll(conn, new String[] { 75 | "INSERT INTO campaigns ( campaignId, name, pace, securityKey) VALUES ( 10, 'Notused', 'RUNNABLE', 'xxx' ) ", 76 | "INSERT INTO hopper ( campaign, runMode) VALUES ( 10, 'KO_D' ) " 77 | 78 | }); 79 | 80 | List lP = ACInfoDB.findAll(conn); 81 | assertEquals( "N info", 1, lP.size()); 82 | assertEquals( "#1: Name", "Notused", lP.get(0).name ); 83 | assertEquals( "#1: ID", 10, lP.get(0).campaignId ); 84 | assertEquals( "#1: SecKey", "xxx", lP.get(0).secKey ); 85 | assertEquals( "#1: Running", false, lP.get(0).isRunning ); 86 | } 87 | 88 | 89 | @Test 90 | public void testPlainExistsRunning() throws SQLException { 91 | 92 | JdbcPattern.execAll(conn, new String[] { 93 | "INSERT INTO campaigns ( campaignId, name, pace, securityKey) VALUES ( 10, 'Notused', 'RUNNABLE', 'xxx' ) ", 94 | "INSERT INTO hopper ( campaign, runMode) VALUES ( 10, 'OK_A' ) " 95 | 96 | }); 97 | 98 | List lP = ACInfoDB.findAll(conn); 99 | assertEquals( "N info", 1, lP.size()); 100 | assertEquals( "#1: Name", "Notused", lP.get(0).name ); 101 | assertEquals( "#1: ID", 10, lP.get(0).campaignId ); 102 | assertEquals( "#1: SecKey", "xxx", lP.get(0).secKey ); 103 | assertEquals( "#1: Running", true, lP.get(0).isRunning ); 104 | 105 | 106 | } 107 | 108 | @Test 109 | public void testPlainExistsRunningIfAtLeastOneRunning() throws SQLException { 110 | 111 | JdbcPattern.execAll(conn, new String[] { 112 | "INSERT INTO campaigns ( campaignId, name, pace, securityKey) VALUES ( 10, 'Notused', 'RUNNABLE', 'xxx' ) ", 113 | "INSERT INTO hopper ( campaign, runMode) VALUES ( 10, 'KO_C' ) ", 114 | "INSERT INTO hopper ( campaign, runMode) VALUES ( 10, 'KO_D' ) ", 115 | "INSERT INTO hopper ( campaign, runMode) VALUES ( 10, 'KO_C' ) ", 116 | "INSERT INTO hopper ( campaign, runMode) VALUES ( 10, 'OK_A' ) " 117 | }); 118 | 119 | List lP = ACInfoDB.findAll(conn); 120 | assertEquals( "N info", 1, lP.size()); 121 | assertEquals( "#1: Name", "Notused", lP.get(0).name ); 122 | assertEquals( "#1: ID", 10, lP.get(0).campaignId ); 123 | assertEquals( "#1: SecKey", "xxx", lP.get(0).secKey ); 124 | assertEquals( "#1: Running", true, lP.get(0).isRunning ); 125 | 126 | } 127 | 128 | 129 | } -------------------------------------------------------------------------------- /tests/ch/loway/oss/ObjectiveSync/learnH2/LearnH2Test.java: -------------------------------------------------------------------------------- 1 | package ch.loway.oss.ObjectiveSync.learnH2; 2 | 3 | import java.sql.Connection; 4 | import java.sql.DriverManager; 5 | import java.sql.SQLException; 6 | import org.junit.After; 7 | import org.junit.AfterClass; 8 | import org.junit.Before; 9 | import org.junit.BeforeClass; 10 | import org.junit.Test; 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * 15 | * @author lenz 16 | */ 17 | public class LearnH2Test { 18 | 19 | public LearnH2Test() { 20 | } 21 | 22 | @BeforeClass 23 | public static void setUpClass() throws Exception { 24 | } 25 | 26 | @AfterClass 27 | public static void tearDownClass() throws Exception { 28 | } 29 | 30 | @Before 31 | public void setUp() { 32 | } 33 | 34 | @After 35 | public void tearDown() { 36 | } 37 | 38 | @Test 39 | public void testInit() throws ClassNotFoundException, SQLException { 40 | 41 | 42 | Class.forName("org.h2.Driver"); 43 | Connection conn = DriverManager.getConnection("jdbc:h2:mem:test"); 44 | conn.close(); 45 | 46 | assertTrue(true); 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/ch/loway/oss/ObjectiveSync/maps/ACInfo.java: -------------------------------------------------------------------------------- 1 | package ch.loway.oss.ObjectiveSync.maps; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | /** 8 | * This is an aggregation query. 9 | * 10 | * @author lenz 11 | */ 12 | public class ACInfo { 13 | 14 | public int campaignId = 0; 15 | public String name = ""; 16 | public boolean isRunning = true; 17 | public String secKey = ""; 18 | 19 | /** 20 | * This is a simple Enum. 21 | */ 22 | public enum RunMode { 23 | 24 | OK_A, OK_B, KO_C, KO_D; 25 | 26 | public static List asList() { 27 | List lOut = new ArrayList(); 28 | for ( RunMode rm: values() ) { 29 | if ( rm.toString().startsWith("OK") ) { 30 | lOut.add(rm); 31 | } 32 | } 33 | return lOut; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/ch/loway/oss/ObjectiveSync/maps/ACInfoDB.java: -------------------------------------------------------------------------------- 1 | 2 | package ch.loway.oss.ObjectiveSync.maps; 3 | 4 | import ch.loway.oss.ObjectiveSync.ObjectiveFetch; 5 | import ch.loway.oss.ObjectiveSync.table.SqlTable; 6 | import java.sql.Connection; 7 | import java.sql.ResultSet; 8 | import java.sql.SQLException; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | /** 14 | * 15 | * 16 | * @author lenz 17 | */ 18 | public class ACInfoDB extends ObjectiveFetch { 19 | 20 | Map results = new HashMap(); 21 | 22 | /** 23 | * Notice a pattern here. 24 | * If we return "null" the object si not added. 25 | * So we return the object only when it's just created. 26 | * If the object is already present, we recover it from the hash table 27 | * and set its value to true. 28 | * 29 | * @param rs 30 | * @return 31 | * @throws SQLException 32 | */ 33 | 34 | @Override 35 | public ACInfo load(ResultSet rs) throws SQLException { 36 | 37 | int id = rs.getInt("campaignId"); 38 | ACInfo ai = null; 39 | 40 | if ( !results.containsKey(id) ) { 41 | ai = new ACInfo(); 42 | ai.campaignId = rs.getInt("campaignId"); 43 | ai.name = rs.getString("name"); 44 | ai.secKey = rs.getString("securityKey"); 45 | ai.isRunning = false; 46 | results.put(id, ai); 47 | } 48 | 49 | String runMode = rs.getString("runMode"); 50 | if ( runMode != null ) { 51 | ACInfo.RunMode rMode = ACInfo.RunMode.valueOf(runMode); 52 | if ( ACInfo.RunMode.asList().contains( rMode ) ) { 53 | 54 | ACInfo ai2 = results.get(id); 55 | ai2.isRunning = true; 56 | } 57 | } 58 | 59 | return ai; 60 | 61 | } 62 | 63 | /** 64 | * Gets back a "real" view. 65 | * 66 | * @param conn 67 | * @return 68 | * @throws SQLException 69 | */ 70 | 71 | public static List findAll( Connection conn ) throws SQLException { 72 | 73 | ACInfoDB aci = new ACInfoDB(); 74 | List lOut = aci.queryDirect(conn, 75 | " SELECT c.campaignId, c.name, c.securityKey, hopper.runMode, count(*) as NUM " 76 | + " FROM campaigns c " 77 | + " LEFT JOIN hopper ON c.campaignId = hopper.campaign " 78 | + " WHERE c.pace = 'RUNNABLE' " 79 | + " GROUP BY c.campaignId, c.name, c.securityKey, hopper.runMode " 80 | ); 81 | 82 | return lOut; 83 | } 84 | 85 | } 86 | 87 | -------------------------------------------------------------------------------- /tests/ch/loway/oss/ObjectiveSync/maps/Organization.java: -------------------------------------------------------------------------------- 1 | 2 | package ch.loway.oss.ObjectiveSync.maps; 3 | 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | /** 8 | * This class matches an organization that might include one or more 9 | * persons. 10 | * 11 | * @author lenz 12 | */ 13 | public class Organization { 14 | 15 | public int id = 0; 16 | public String name = ""; 17 | public Set members = new HashSet(); 18 | 19 | /** 20 | * Builds this object. 21 | * 22 | * @param id 23 | * @param name 24 | * @return 25 | */ 26 | 27 | public static Organization build( int id, String name ) { 28 | Organization o = new Organization(); 29 | o.id = id; 30 | o.name = name; 31 | return o; 32 | } 33 | 34 | /** 35 | * Add a member to this org. 36 | * 37 | * @param p 38 | */ 39 | public void addMember( Person p ) { 40 | members.add(p); 41 | } 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /tests/ch/loway/oss/ObjectiveSync/maps/OrganizationDB.java: -------------------------------------------------------------------------------- 1 | package ch.loway.oss.ObjectiveSync.maps; 2 | 3 | import ch.loway.oss.ObjectiveSync.ObjectiveFetch; 4 | import ch.loway.oss.ObjectiveSync.SqlTools; 5 | import ch.loway.oss.ObjectiveSync.table.SqlField; 6 | import ch.loway.oss.ObjectiveSync.table.SqlTable; 7 | import ch.loway.oss.ObjectiveSync.table.type.IntValue; 8 | import ch.loway.oss.ObjectiveSync.table.type.StringValue; 9 | import ch.loway.oss.ObjectiveSync.updater.deferred.DeferredLoadByParent; 10 | import ch.loway.oss.ObjectiveSync.updater.FieldSet; 11 | import java.sql.Connection; 12 | import java.sql.ResultSet; 13 | import java.sql.SQLException; 14 | import java.util.Set; 15 | 16 | /** 17 | * This object manages the persstence for Organization objects, that are mainly 18 | * interesting because they manage an 1:N relationship to Persons. 19 | * 20 | * @author lenz 21 | */ 22 | public class OrganizationDB extends ObjectiveFetch { 23 | 24 | /** 25 | * Defines how my table looks like on DB. 26 | * 27 | * I put in all the information I need for updating it. 28 | * 29 | * @return 30 | */ 31 | 32 | @Override 33 | public SqlTable table() { 34 | return new SqlTable("org") 35 | .field(SqlField.pk("id", "int auto_increment", null)) 36 | .field(SqlField.str("name", "char(50)", null, null)); 37 | } 38 | 39 | /** 40 | * Loads an object of class Organization. 41 | * 42 | * Notice how we use a DeferredLoader that holds a reference to the object 43 | * being created so that after we have created objects, we can query the 44 | * database again and load other objects by their PK. This means we can coalesce 45 | * PK accesses. 46 | * 47 | * @param rs 48 | * @return 49 | * @throws SQLException 50 | */ 51 | 52 | @Override 53 | public Organization load(ResultSet rs) throws SQLException { 54 | final Organization org = new Organization(); 55 | org.id = rs.getInt("id"); 56 | org.name = rs.getString("name"); 57 | 58 | // we load all kids for this parent 59 | deferLoading( new DeferredLoadByParent() { 60 | @Override 61 | public void update(Set sons) { 62 | org.members = sons; 63 | } 64 | }.setup( new PersonDB(), org.id )); 65 | 66 | return org; 67 | 68 | } 69 | 70 | /** 71 | * Saves an object of class Organization and all its kids. 72 | * 73 | * Note that we currently do not set the PK if it's not set. 74 | * 75 | * @param p The object to be saved 76 | * @param su Th 77 | * e fieldSet where I will set values. 78 | * @throws SQLException 79 | */ 80 | 81 | @Override 82 | public void save(Organization p, FieldSet su) throws SQLException { 83 | if (p.id > 0) { 84 | su.set("id", new IntValue(p.id)); 85 | } 86 | su.set("name", new StringValue(p.name)); 87 | } 88 | 89 | @Override 90 | public void saveSubObjects(Connection conn, Organization obj) throws SQLException { 91 | PersonDB db = new PersonDB(); 92 | db.commitAll(conn, obj.members, Integer.toString(obj.id) ); 93 | } 94 | 95 | /** 96 | * Updates the PK on insert. 97 | * 98 | * @param pkFromDb 99 | */ 100 | @Override 101 | public void updatePrimaryKey(Organization object, String pkFromDb) { 102 | object.id = SqlTools.cint(pkFromDb); 103 | } 104 | 105 | 106 | 107 | 108 | } 109 | -------------------------------------------------------------------------------- /tests/ch/loway/oss/ObjectiveSync/maps/Person.java: -------------------------------------------------------------------------------- 1 | 2 | package ch.loway.oss.ObjectiveSync.maps; 3 | 4 | /** 5 | * 6 | * 7 | * $Id$ 8 | * @author lenz 9 | */ 10 | public class Person { 11 | 12 | public int id =0; 13 | public String name = ""; 14 | public String surname = ""; 15 | 16 | public static Person build( int id, String name, String surn) { 17 | Person p = new Person(); 18 | p.id = 0; 19 | p.name = name; 20 | p.surname = surn; 21 | return p; 22 | } 23 | 24 | } 25 | 26 | // $Log$ 27 | // 28 | -------------------------------------------------------------------------------- /tests/ch/loway/oss/ObjectiveSync/maps/PersonDB.java: -------------------------------------------------------------------------------- 1 | package ch.loway.oss.ObjectiveSync.maps; 2 | 3 | import ch.loway.oss.ObjectiveSync.ObjectiveFetch; 4 | import ch.loway.oss.ObjectiveSync.SqlTools; 5 | import ch.loway.oss.ObjectiveSync.table.SqlField; 6 | import ch.loway.oss.ObjectiveSync.table.SqlTable; 7 | import ch.loway.oss.ObjectiveSync.table.type.IntValue; 8 | import ch.loway.oss.ObjectiveSync.table.type.StringValue; 9 | import ch.loway.oss.ObjectiveSync.updater.FieldSet; 10 | import java.sql.ResultSet; 11 | import java.sql.SQLException; 12 | 13 | /** 14 | * This object manages the persstence for Person objects. 15 | * You coud create it as an inner static class of Person, but for this example 16 | * it is easier if we just keep it separate. 17 | * 18 | * @author lenz 19 | */ 20 | public class PersonDB extends ObjectiveFetch { 21 | 22 | /** 23 | * Defines how my table looks like on DB. 24 | * 25 | * I put in all the information I need for updating it. 26 | * 27 | * @return 28 | */ 29 | 30 | @Override 31 | public SqlTable table() { 32 | return new SqlTable("person") 33 | .field(SqlField.pk("id", "int auto_increment", null)) 34 | .field(SqlField.str("name", "char(50)", null, null)) 35 | .field(SqlField.str("surname", "char(50)", null, null)) 36 | .field(SqlField.parentPk("org_id", "int", null)) 37 | ; 38 | } 39 | 40 | /** 41 | * Loads an obcet of class Person. 42 | * 43 | * @param rs 44 | * @return 45 | * @throws SQLException 46 | */ 47 | 48 | @Override 49 | public Person load(ResultSet rs) throws SQLException { 50 | Person p = new Person(); 51 | p.id = rs.getInt("id"); 52 | p.name = rs.getString("name"); 53 | p.surname = rs.getString("surname"); 54 | return p; 55 | } 56 | 57 | /** 58 | * Saves an object of class Person. 59 | * Note that we currently do not set the PK if it's not set. 60 | * 61 | * @param p The object to be saved 62 | * @param su The fieldSet where I will set values. 63 | * @throws SQLException 64 | */ 65 | 66 | @Override 67 | public void save(Person p, FieldSet su) throws SQLException { 68 | if (p.id > 0) { 69 | su.set("id", new IntValue(p.id)); 70 | } 71 | su.set("name", new StringValue(p.name)); 72 | su.set("surname", new StringValue(p.surname)); 73 | } 74 | 75 | 76 | /** 77 | * Updates the PK on insert. 78 | * 79 | * @param pkFromDb 80 | */ 81 | @Override 82 | public void updatePrimaryKey(Person object, String pkFromDb) { 83 | object.id = SqlTools.cint(pkFromDb); 84 | } 85 | 86 | 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /tests/ch/loway/oss/ObjectiveSync/table/SqlTableTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | 6 | package ch.loway.oss.ObjectiveSync.table; 7 | 8 | import org.junit.After; 9 | import org.junit.AfterClass; 10 | import org.junit.Before; 11 | import org.junit.BeforeClass; 12 | import org.junit.Test; 13 | import static org.junit.Assert.*; 14 | 15 | /** 16 | * 17 | * @author lenz 18 | */ 19 | public class SqlTableTest { 20 | 21 | public SqlTableTest() { 22 | } 23 | 24 | @BeforeClass 25 | public static void setUpClass() throws Exception { 26 | } 27 | 28 | @AfterClass 29 | public static void tearDownClass() throws Exception { 30 | } 31 | 32 | @Before 33 | public void setUp() { 34 | } 35 | 36 | @After 37 | public void tearDown() { 38 | } 39 | 40 | @Test 41 | public void testWritingStyle() { 42 | 43 | SqlTable s = new SqlTable("Hello") 44 | .field( SqlField.pk( "id", "INT(11)", "") ) 45 | .field( SqlField.str( "name", "VARCHAR(50)", null, null) ) 46 | .field( SqlField.i( "age", "INT(11)", null, null) ); 47 | 48 | assertEquals( "PK", "id", s.getPk().name ); 49 | } 50 | 51 | } --------------------------------------------------------------------------------