├── .gitignore ├── .travis.yml ├── .yo-rc.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle ├── config │ ├── checkstyle │ │ ├── checkstyle.xml │ │ └── html-report-style.xsl │ ├── codenarc │ │ └── codenarc.xml │ ├── findbugs │ │ ├── exclude.xml │ │ └── html-report-style.xsl │ └── pmd │ │ └── pmd.xml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── integration-test ├── groovy │ └── org │ │ └── restonfire │ │ └── integrationTests │ │ ├── AbstractTest.groovy │ │ ├── EventStreamTest.groovy │ │ ├── FirebaseSecurityRulesTest.groovy │ │ ├── GetShallowValueOperationTest.groovy │ │ ├── GetValueOperationTest.groovy │ │ ├── OrderByValueOperationTest.groovy │ │ ├── OtherOrderByOperationTest.groovy │ │ ├── PriorityOperationTest.groovy │ │ ├── RemoveValueOperationTest.groovy │ │ ├── SetValueOperationTest.groovy │ │ ├── UpdateValueOperationTest.groovy │ │ └── data │ │ └── SampleData.java └── resources │ ├── logback.xml │ └── org │ └── restonfire │ └── .gitkeep ├── main ├── java │ └── org │ │ └── restonfire │ │ ├── .gitkeep │ │ ├── BaseFirebaseRestDatabaseFactory.java │ │ ├── FirebaseDocumentLocation.java │ │ ├── FirebaseRestDatabase.java │ │ ├── FirebaseRestDatabaseFactory.java │ │ ├── FirebaseRestDatabaseImpl.java │ │ ├── FirebaseRestEventStream.java │ │ ├── FirebaseRestEventStreamImpl.java │ │ ├── FirebaseRestQuery.java │ │ ├── FirebaseRestQueryImpl.java │ │ ├── FirebaseRestReference.java │ │ ├── FirebaseRestReferenceImpl.java │ │ ├── FirebaseSecurityRulesReference.java │ │ ├── FirebaseSecurityRulesReferenceImpl.java │ │ ├── PathUtil.java │ │ ├── RequestBuilderUtil.java │ │ ├── RestUtil.java │ │ ├── ServerValues.java │ │ ├── StringUtil.java │ │ ├── exceptions │ │ ├── FirebaseAccessException.java │ │ ├── FirebaseAuthenticationExpiredException.java │ │ ├── FirebaseInvalidStateException.java │ │ ├── FirebaseRestException.java │ │ └── FirebaseRuntimeException.java │ │ └── responses │ │ ├── FirebaseSecurityRules.java │ │ ├── PushResponse.java │ │ ├── StreamingEvent.java │ │ └── StreamingEventData.java └── resources │ └── org │ └── restonfire │ └── .gitkeep └── test ├── java └── org │ └── restonfire │ ├── .gitkeep │ ├── BaseFirebaseRestDatabaseFactoryTest.java │ ├── FirebaseRestDatabaseImplTest.java │ ├── FirebaseRestEventStreamImplTest.java │ ├── FirebaseRestQueryImplTest.java │ ├── FirebaseRestReferenceImplTest.java │ ├── FirebaseSecurityRulesReferenceImplTest.java │ ├── PathUtilTest.java │ ├── RequestBuilderUtilTest.java │ ├── StringUtilTest.java │ ├── fakes │ ├── FakeResponseHeaders.java │ └── FakeResponseStatus.java │ ├── responses │ └── StreamingEventTest.java │ ├── testdata │ └── SampleData.java │ └── testutils │ ├── AbstractMockTestCase.java │ └── MockObjectHelper.java └── resources ├── logback.xml └── org └── restonfire └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | # Created with https://www.gitignore.io 2 | 3 | ### Gradle ### 4 | .gradle/ 5 | build/ 6 | 7 | # Ignore Gradle GUI config 8 | gradle-app.setting 9 | 10 | ### JetBrains ### 11 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 12 | 13 | /*.iml 14 | 15 | ## Directory-based project format: 16 | .idea/ 17 | 18 | ## File-based project format: 19 | *.ipr 20 | *.iws 21 | 22 | ## Plugin-specific files: 23 | 24 | # IntelliJ 25 | out/ 26 | 27 | # mpeltonen/sbt-idea plugin 28 | .idea_modules/ 29 | 30 | # JIRA plugin 31 | atlassian-ide-plugin.xml 32 | 33 | # Crashlytics plugin (for Android Studio and IntelliJ) 34 | com_crashlytics_export_strings.xml 35 | 36 | 37 | ### Eclipse ### 38 | *.pydevproject 39 | .metadata 40 | bin/ 41 | tmp/ 42 | *.tmp 43 | *.bak 44 | *.swp 45 | *~.nib 46 | local.properties 47 | .settings/ 48 | .loadpath 49 | 50 | # External tool builders 51 | .externalToolBuilders/ 52 | 53 | # Locally stored "Eclipse launch configurations" 54 | *.launch 55 | 56 | # CDT-specific 57 | .cproject 58 | 59 | # PDT-specific 60 | .buildpath 61 | 62 | # sbteclipse plugin 63 | .target 64 | 65 | # TeXlipse plugin 66 | .texlipse 67 | 68 | ### Java ### 69 | *.class 70 | 71 | # Mobile Tools for Java (J2ME) 72 | .mtj.tmp/ 73 | 74 | # Package Files # 75 | *.war 76 | *.ear 77 | 78 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 79 | hs_err_pid* 80 | 81 | 82 | ### NetBeans ### 83 | nbproject/private/ 84 | nbbuild/ 85 | dist/ 86 | nbdist/ 87 | nbactions.xml 88 | nb-configuration.xml 89 | 90 | 91 | ### OSX ### 92 | .DS_Store 93 | .AppleDouble 94 | .LSOverride 95 | 96 | # Icon must end with two \r 97 | Icon 98 | 99 | 100 | # Thumbnails 101 | ._* 102 | 103 | # Files that might appear on external disk 104 | .Spotlight-V100 105 | .Trashes 106 | 107 | # Directories potentially created on remote AFP share 108 | .AppleDB 109 | .AppleDesktop 110 | Network Trash Folder 111 | Temporary Items 112 | .apdisk 113 | 114 | 115 | ### Windows ### 116 | # Windows image file caches 117 | Thumbs.db 118 | ehthumbs.db 119 | 120 | # Folder config file 121 | Desktop.ini 122 | 123 | # Recycle Bin used on file shares 124 | $RECYCLE.BIN/ 125 | 126 | # Windows Installer files 127 | *.cab 128 | *.msi 129 | *.msm 130 | *.msp 131 | 132 | # Windows shortcuts 133 | *.lnk 134 | 135 | 136 | ### Linux ### 137 | *~ 138 | 139 | # KDE directory preferences 140 | .directory 141 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk7 4 | - oraclejdk8 5 | 6 | sudo: false 7 | 8 | before_install: 9 | - chmod +x gradlew 10 | 11 | install: 12 | - ./gradlew assemble 13 | 14 | script: 15 | - ./gradlew check -x integrationTest 16 | 17 | after_success: 18 | - ./gradlew jacocoTestReport coveralls 19 | 20 | cache: 21 | directories: 22 | - $HOME/.gradle/caches/ -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-lib-java": { 3 | "githubUser": "j-fischer", 4 | "authorName": "Johannes Fischer", 5 | "authorEmail": "jh-fischer@web.de", 6 | "libName": "rest-on-fire", 7 | "libGroup": "org.restonfire", 8 | "libPackage": "org.restonfire", 9 | "libVersion": "0.1.0", 10 | "libDesc": "Firebase REST API wrapper for Java", 11 | "targetJava": "1.7", 12 | "libTags": "firebase,rest,api,wrapper", 13 | "bintrayUser": "j-fischer", 14 | "bintrayRepo": "rest-on-fire", 15 | "bintraySignFiles": true, 16 | "mavenCentralSync": true, 17 | "enableQualityChecks": true, 18 | "usedGeneratorVersion": "2.0.0" 19 | } 20 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.7.0 (2016-06-26) 2 | 3 | * Added support for retrieving shallow values to FirebaseRestReference interface 4 | * Upgraded external libraries: Gson (2.7), SLF4J (1.7.21) 5 | 6 | ### 0.6.0 (2016-06-22) 7 | 8 | * Added support (get, set, remove) for Firebase priorities to the FirebaseRestReference interface 9 | 10 | ### 0.5.0 (2016-06-13) 11 | 12 | * Added FirebaseRestQuery implementation that allows for sorting and filtering of result sets 13 | 14 | ### 0.4.0 (2016-05-28) 15 | 16 | * Added FirebaseSecurityRulesReference implementation to support retrieving and setting of the security rules 17 | 18 | ### 0.3.0 (2016-05-24) 19 | * Breaking change: Renamed all references from "namespace" to "database" in order to comply with Firebase's new naming conventions 20 | 21 | ### 0.2.0 (2016-05-22) 22 | * Changed group ID of package to com.github.j-fischer 23 | * Added FirebaseRestEventStream implementation to support Firebase's REST streaming protocol 24 | * Introduced error codes for easier evaluation of exception root causes 25 | 26 | ### 0.1.0 (2016-05-04) 27 | * Initial release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 Johannes Fischer 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Rest On Fire 2 | [![License](https://img.shields.io/hexpm/l/plug.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0) 3 | [![Build Status](http://img.shields.io/travis/j-fischer/rest-on-fire.svg?style=flat&branch=master)](https://travis-ci.org/j-fischer/rest-on-fire) 4 | [![Coverage Status](https://img.shields.io/coveralls/j-fischer/rest-on-fire.svg?style=flat)](https://coveralls.io/r/j-fischer/rest-on-fire?branch=master) 5 | [![JCenter](https://img.shields.io/bintray/v/j-fischer/maven/rest-on-fire.svg?label=jcenter)](https://bintray.com/j-fischer/maven/rest-on-fire/_latestVersion) 6 | [![Maven Central](https://img.shields.io/maven-central/v/com.github.j-fischer/rest-on-fire.svg?style=flat)](https://maven-badges.herokuapp.com/maven-central/com.github.j-fischer/rest-on-fire) 7 | 8 | ### About 9 | 10 | This library is a wrapper for Firebase's REST API written in Java. 11 | 12 | Why use the REST API? While Firebase's Android and Server SDKs are great and the REST API is not meant to be a 13 | replacement, it serves some purpose in server side environments. For example, if your server environment does 14 | not allow for a persistent socket connection to be opened, the REST API is the way to go. Also, Firebase's SDKs, 15 | to my knowledge, cache all values you retrieve for offline availability. Those caches are unbound, which means 16 | that the grow indefinitely over time on a system with 24/7 uptime. To avoid out-of-memory exceptions, the REST API 17 | can be used to have more control over the heap. Last, the REST API provides some unique features such as shallow 18 | queries, which will only retrieve the keys at a location and not the values, which might be required in some situations. 19 | 20 | To sum it up, always look at the Firebase SDKs first before switching to the REST API as they offer a lot more features 21 | and are continuously improved. But if REST is the better option in your case, try out Rest On Fire as it supports 22 | all relevant REST features with a very similar API than Firebase's SDKs. 23 | 24 | Features: 25 | * Similar interfaces to the official Firebase APIs (Java/Javascript) for an easier adoption 26 | * Supports all basic operations: get, set, update, push & remove 27 | * Supports sorting and filtering of the data, including priorities 28 | * Supports streaming events through the REST API 29 | * Supports retrieval of shallow values to help work with large data sets 30 | * Supports retrieving and setting of Firebase security rules 31 | * Supports functional programming style through JDeferred's Promises 32 | * Uses SLF4J for logging to allow for an easy adoption of the library's logs 33 | 34 | This library uses [Ning's AsyncHttpClient](http://www.ning.com/code/2010/03/introducing-nings-asynchronous-http-client-library/), 35 | [JDeferred](https://github.com/jdeferred/jdeferred), [Gson](https://github.com/google/gson) and [SLF4J](http://www.slf4j.org/) 36 | as external dependencies. 37 | 38 | Additional information on the AsyncHttpClient can be found [here](https://jfarcand.wordpress.com/2010/12/21/going-asynchronous-using-asynchttpclient-the-basic/). 39 | 40 | Also, see the [Firebase documentation](https://firebase.google.com/docs/database/rest/retrieve-data) for more information 41 | 42 | ### Setup 43 | 44 | Releases are published to [bintray jcenter](https://bintray.com/bintray/jcenter) (package appear immediately after release) 45 | and then to maven central (require few days after release to be published). 46 | 47 | Maven: 48 | 49 | ```xml 50 | 51 | com.github.j-fischer 52 | rest-on-fire 53 | 0.7.0 54 | 55 | ``` 56 | 57 | Gradle: 58 | 59 | ```groovy 60 | compile 'com.github.j-fischer:rest-on-fire:0.7.0' 61 | ``` 62 | 63 | ***Please check the badge on the top of this page for the latest release version!!!*** 64 | 65 | ### Usage 66 | 67 | Start by creating a factory for FirebaseDatabases. The factory manages the external 68 | dependencies for all databases. 69 | 70 | ```java 71 | BaseFirebaseRestDatabaseFactory factory = new BaseFirebaseRestDatabaseFactory( 72 | new AsyncHttpClient(), 73 | new GsonBuilder().create() 74 | ); 75 | ``` 76 | 77 | Once the factory is created, you can create an instance of a FirebaseRestDatabase. 78 | 79 | ```java 80 | // Expects that Firebase access rules do not require authentication 81 | FirebaseRestDatabase database = factory.create( 82 | "http://database123.firebaseio.com", 83 | null // accessToken 84 | ); 85 | ``` 86 | 87 | The database can be used to create a FirebaseRestReference object, which is similar 88 | to [Firebase](https://www.firebase.com/docs/android/api/#firebase_methods) object of the Java APIs. 89 | 90 | ```java 91 | FirebaseRestReference ref = database.getReference("some/location"); 92 | ``` 93 | 94 | The reference object can then be used to perform operations like retrieving or setting 95 | the value. 96 | 97 | ```java 98 | ref.getValue(String.class) 99 | .done(new DoneCallback() { 100 | @Override 101 | void onDone(String result) { 102 | System.out.println(result); 103 | } 104 | }); 105 | ``` 106 | 107 | The `FirebaseRestReference` also provides a `query()` function to apply advanced sorting and filtering 108 | to the result set. The function returns a `FirebaseRestQuery` object instance that provides a simple 109 | interface to define and run the query. 110 | 111 | ```java 112 | FirebaseRestQuery query = ref.query(); 113 | ``` 114 | 115 | Assuming Firebase's dinosaur sample database, a query for all dinausaurs under a specific height would look like the following. 116 | 117 | ```java 118 | query 119 | .orderByChild("height") 120 | .endAt(2.1) 121 | .run(Map.class) 122 | .done(new DoneCallback>() { 123 | @Override 124 | void onDone(Map result) { 125 | // Process the result, which is a Map with the dinosaur's name 126 | // as the key and a Map of its properties as a value. 127 | } 128 | }); 129 | ``` 130 | 131 | The database can also be used to create a FirebaseRestEventStream object, which allows for listening 132 | for changes to locations within the database. 133 | 134 | ```java 135 | FirebaseRestEventStream eventStream = database.getEventStream("some/location"); 136 | ``` 137 | 138 | The actual events are published through the progress handler of the Promise. 139 | 140 | ```java 141 | eventStream 142 | .startListening() 143 | .progress(new ProgressCallback() { 144 | @Override 145 | void onProgress(StreamingEvent event) { 146 | StreamingEvent.EventType eventType = event.getEventType(); 147 | StreamingEventData eventData = event.getEventData(); // see override with TypeToken for more options 148 | 149 | String path = eventData.getPath(); 150 | Object value = eventData.getData(); // the type depends on the value stored at the event location 151 | }) 152 | .always(new AlwaysCallback() { 153 | @Override 154 | void onAlways(Promise.State state, Void resolved, FirebaseRuntimeException rejected) { 155 | assert state == Primise.State.RESOLVED; 156 | assert resolved == null; 157 | assert rejected == null; 158 | } 159 | }); 160 | ``` 161 | 162 | If you want to use this API to retrieve or update the Firebase security rules, use the FirebaseSecurityRulesReference to 163 | access. However, it's important to note that accessing the security rules requires elevated access privileges, 164 | such as the Firebase secret. 165 | 166 | ```java 167 | FirebaseSecurityRulesReference securityRulesRef = database.getSecurityRules(); 168 | ``` 169 | 170 | The security rules can now be retrieved, modified and written back to the database. 171 | 172 | ```java 173 | securityRulesRef 174 | .get() 175 | .done(new DoneCallback() { 176 | @Override 177 | void onDone(FirebaseSecurityRules currentRules) { 178 | Map rulesMap = currentRules.getRules(); 179 | 180 | // Let's assume that the default read permission is set to false 181 | rules.put(".read", true); 182 | 183 | FirebaseSecurityRules newRules = new FirebaseSecurityRules(rules); 184 | 185 | // set returns a promise that will resolve after the request succeeded 186 | securityRulesRef.set(newRules); 187 | } 188 | }); 189 | ``` 190 | 191 | Please take a look at [JDeferred's documentation](https://github.com/jdeferred/jdeferred) for 192 | more information on the promises and its callback interfaces. 193 | 194 | For more information, please take a look at the Rest On Fire's API documentation. 195 | 196 | - 197 | [![java lib generator](http://img.shields.io/badge/Powered%20by-%20Java%20lib%20generator-green.svg?style=flat-square)](https://github.com/xvik/generator-lib-java) -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'idea' 3 | id 'groovy' 4 | id 'jacoco' 5 | id 'project-report' 6 | id 'ru.vyarus.java-lib' version '1.0.1' 7 | id 'ru.vyarus.github-info' version '1.0.0' 8 | id 'ru.vyarus.animalsniffer' version '1.0.0' 9 | id 'ru.vyarus.quality' version '1.2.0' 10 | id 'com.github.kt3k.coveralls' version '2.4.0x' 11 | id 'com.jfrog.bintray' version '1.5' 12 | id 'net.researchgate.release' version '2.3.4' 13 | id 'com.github.ben-manes.versions' version '0.11.3' 14 | id 'net.saliman.cobertura' version '2.3.1' 15 | } 16 | 17 | sourceCompatibility = 1.7 18 | targetCompatibility = 1.7 19 | 20 | wrapper { 21 | gradleVersion = 2.10 22 | } 23 | 24 | repositories { jcenter(); mavenCentral(); mavenLocal() } 25 | 26 | 27 | sourceSets { 28 | integrationTest { 29 | groovy { 30 | compileClasspath += main.output 31 | runtimeClasspath += main.output 32 | srcDir file('src/integration-test/groovy') 33 | } 34 | resources.srcDir file('src/integration-test/resources') 35 | } 36 | } 37 | 38 | configurations { 39 | integrationTestCompile.extendsFrom testCompile 40 | integrationTestRuntime.extendsFrom testRuntime 41 | } 42 | 43 | dependencies { 44 | signature 'org.codehaus.mojo.signature:java17:+@signature' 45 | 46 | provided 'com.google.code.findbugs:jsr305:3.0.1' 47 | provided 'com.google.code.findbugs:annotations:3.0.1' 48 | 49 | compile 'org.slf4j:slf4j-api:1.7.21' 50 | compile 'com.google.code.gson:gson:2.7' 51 | compile 'com.ning:async-http-client:1.9.38' 52 | compile 'org.jdeferred:jdeferred-core:1.2.4' 53 | 54 | testCompile 'org.apache.commons:commons-lang3:3.4' 55 | testCompile 'ch.qos.logback:logback-classic:1.1.7' 56 | testCompile 'org.slf4j:jul-to-slf4j:1.7.21' 57 | testCompile 'org.spockframework:spock-core:1.0-groovy-2.4' 58 | testCompile 'org.jmock:jmock-legacy:2.5.1' 59 | testCompile 'junit-addons:junit-addons:1.4' 60 | 61 | integrationTestCompile 'com.firebase:firebase-token-generator:2.0.0' 62 | } 63 | 64 | group = 'com.github.j-fischer' 65 | description = 'Firebase REST API wrapper for Java' 66 | 67 | github { 68 | user = 'j-fischer' 69 | license = 'Apache' 70 | } 71 | 72 | pom { 73 | developers { 74 | developer { 75 | id "j-fischer" 76 | name "Johannes Fischer" 77 | email "fischer.jh@gmail.com" 78 | } 79 | } 80 | } 81 | 82 | bintray { 83 | user = project.hasProperty('bintrayUser') ? bintrayUser : 'USER' 84 | key = project.hasProperty('bintrayKey') ? bintrayKey : 'KEY' 85 | publications = ['maven'] 86 | dryRun = false 87 | publish = true 88 | pkg { 89 | repo = 'maven' 90 | name = project.name 91 | desc = project.description 92 | labels = ['firebase', 'rest', 'api', 'wrapper'] 93 | publicDownloadNumbers = true 94 | licenses = ['Apache-2.0'] 95 | version { 96 | gpg { 97 | sign = true 98 | passphrase = project.hasProperty('gpgPassphrase') ? gpgPassphrase : '' 99 | } 100 | mavenCentralSync { 101 | sync = true 102 | user = project.hasProperty('sonatypeUser') ? sonatypeUser : 'USER' 103 | password = project.hasProperty('sonatypePassword') ? sonatypePassword : 'PASSWORD' 104 | } 105 | } 106 | } 107 | } 108 | 109 | afterReleaseBuild { 110 | dependsOn = [bintrayUpload] 111 | doLast { 112 | logger.warn "RELEASED $project.group:$project.name:$project.version" 113 | } 114 | } 115 | 116 | afterEvaluate { 117 | checkstyle { 118 | ignoreFailures = true 119 | } 120 | 121 | findbugs { 122 | ignoreFailures = true 123 | } 124 | 125 | pmd { 126 | ignoreFailures = true 127 | } 128 | } 129 | 130 | cobertura { 131 | coverageFormats = ['html', 'xml'] 132 | coverageIgnoreTrivial = true 133 | coverageIgnores = ['org.slf4j.Logger.*'] 134 | coverageReportDir = new File("$buildDir/reports/cobertura") 135 | } 136 | 137 | test { 138 | testLogging { 139 | events "skipped", "failed", "standard_error" 140 | exceptionFormat "full" 141 | } 142 | maxHeapSize = "512m" 143 | } 144 | test.finalizedBy(project.tasks.cobertura) 145 | 146 | task integrationTest(type: Test) { 147 | testClassesDir = sourceSets.integrationTest.output.classesDir 148 | classpath = sourceSets.integrationTest.runtimeClasspath 149 | 150 | testLogging { 151 | events "skipped", "failed", "standard_error" 152 | exceptionFormat "full" 153 | } 154 | 155 | systemProperties System.properties 156 | 157 | outputs.upToDateWhen { false } 158 | } 159 | 160 | tasks.withType(Test) { 161 | reports.html.destination = file("${reporting.baseDir}/${name}") 162 | } 163 | 164 | dependencyUpdates.revision = 'release' 165 | jacocoTestReport.reports.xml.enabled = true 166 | 167 | task showDependenciesTree(dependsOn: 'htmlDependencyReport', group: 'help', description: 168 | 'Generates dependencies tree report and opens it in browser') << { 169 | java.awt.Desktop.getDesktop().open(file('/build/reports/project/dependencies/root.html')) 170 | } 171 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=0.8.0-SNAPHSOT -------------------------------------------------------------------------------- /gradle/config/checkstyle/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | -------------------------------------------------------------------------------- /gradle/config/checkstyle/html-report-style.xsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 83 | 84 | 85 | 86 | 87 | 88 |
79 | 82 |

CheckStyle Audit

Designed for use with CheckStyle and Ant.
89 |
90 | 91 | 92 | 93 |
94 | 95 | 96 | 97 |
98 | 99 | 100 | 101 | 102 |
103 | 104 | 105 | 106 | 107 |
108 | 109 | 110 | 111 | 112 |

Files

113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 |
NameErrors
129 |
130 | 131 | 132 | 133 | 134 |

File

135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 |
Error DescriptionLine
150 | Back to top 151 |
152 | 153 | 154 | 155 |

Summary

156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 |
FilesErrors
169 |
170 | 171 | 172 | 173 | a 174 | b 175 | 176 | 177 |
178 | 179 | 180 | -------------------------------------------------------------------------------- /gradle/config/codenarc/codenarc.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /gradle/config/findbugs/exclude.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /gradle/config/pmd/pmd.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j-fischer/rest-on-fire/2e264b14ed0cbff156d2a625b139fc7c2df4bd56/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jan 03 22:59:17 NOVT 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'rest-on-fire' 2 | 3 | -------------------------------------------------------------------------------- /src/integration-test/groovy/org/restonfire/integrationTests/AbstractTest.groovy: -------------------------------------------------------------------------------- 1 | package org.restonfire.integrationTests 2 | 3 | import com.firebase.security.token.TokenGenerator 4 | import com.google.gson.Gson 5 | import com.google.gson.GsonBuilder 6 | import com.ning.http.client.AsyncHttpClient 7 | import com.ning.http.client.AsyncHttpClientConfig 8 | import org.jdeferred.Promise 9 | import org.restonfire.BaseFirebaseRestDatabaseFactory 10 | import org.restonfire.FirebaseRestDatabase 11 | import org.jdeferred.impl.DefaultDeferredManager 12 | import org.restonfire.exceptions.FirebaseRuntimeException 13 | import org.restonfire.responses.FirebaseSecurityRules 14 | import spock.lang.Specification 15 | import spock.util.concurrent.AsyncConditions 16 | /** 17 | * Base class for tests managing Firebase setup and token generation. 18 | */ 19 | abstract class AbstractTest extends Specification { 20 | 21 | private AsyncHttpClientConfig.Builder builder = new AsyncHttpClientConfig.Builder(); 22 | 23 | private Gson gson = new GsonBuilder().create() 24 | private String namespaceUrl = System.getProperty("firebase.namespace") ?: System.getenv("FIREBASE_NAMESPACE") 25 | private String firebaseSecret = System.getProperty("firebase.secret") ?: System.getenv("FIREBASE_SECRET") 26 | 27 | private String firebaseToken 28 | private Promise, FirebaseRuntimeException, Void> setupPromise 29 | 30 | private BaseFirebaseRestDatabaseFactory factory 31 | 32 | void setup() { 33 | AsyncConditions cond = new AsyncConditions() 34 | 35 | factory = new BaseFirebaseRestDatabaseFactory( 36 | new AsyncHttpClient(builder 37 | .setCompressionEnforced(true) 38 | .setAllowPoolingConnections(true) 39 | .build() 40 | ), 41 | gson 42 | ) 43 | 44 | def adminNamespace = factory.create( 45 | namespaceUrl, 46 | firebaseSecret 47 | ) 48 | 49 | def securityPromise = adminNamespace 50 | .getSecurityRules() 51 | .set(getDefaultSecurityRules()) 52 | 53 | def dataPromise = adminNamespace 54 | .getReference("/") 55 | .setValue(getDefaultDataSet()) 56 | .always({ Promise.State state, Object val, FirebaseRuntimeException ex -> 57 | cond.evaluate{ 58 | assert state == Promise.State.RESOLVED 59 | } 60 | }) 61 | 62 | setupPromise = new DefaultDeferredManager() 63 | .when(securityPromise, dataPromise) 64 | 65 | def payload = [ 66 | uid: "1" 67 | ] 68 | 69 | def tokenGenerator = new TokenGenerator(firebaseSecret) 70 | firebaseToken = tokenGenerator.createToken(payload) 71 | 72 | cond.await(10) 73 | } 74 | 75 | FirebaseRestDatabase createNamespace() { 76 | assert firebaseToken != null 77 | 78 | return factory.create( 79 | namespaceUrl, 80 | firebaseToken 81 | ) 82 | } 83 | 84 | FirebaseRestDatabase createNamespaceWithSecret() { 85 | return factory.create( 86 | namespaceUrl, 87 | firebaseSecret 88 | ) 89 | } 90 | 91 | private Map getDefaultDataSet() { 92 | def defaultData = [ 93 | testData: [ 94 | sampleData: [ foo: "bar" ], 95 | integer: 1, 96 | bool: true, 97 | text: "aString", 98 | toBeSet: "SET ME", 99 | toBeRemoved: "DELETE ME", 100 | toBeUpdated: [ foo: "bar" ], 101 | sortedStrings: [ 102 | c: "y", 103 | b: "z", 104 | a: "x" 105 | ], 106 | sortedStringsWithPriorities: [ 107 | c: [ ".value": "y", ".priority": 1.0 ], 108 | b: [ ".value": "z", ".priority": 2.0 ], 109 | a: [ ".value": "x", ".priority": 3.0 ] 110 | ], 111 | dinosaurs: [ 112 | lambeosaurus: [ 113 | height : 2.1, 114 | length : 12.5, 115 | weight: 5000 116 | ], 117 | stegosaurus: [ 118 | height : 4, 119 | length : 9, 120 | weight : 2500 121 | ] 122 | ] 123 | ], 124 | noReadAccess: "BIG SECRET" 125 | ] 126 | 127 | return defaultData 128 | } 129 | 130 | FirebaseSecurityRules getDefaultSecurityRules() { 131 | def rulesJson = '''{ 132 | "rules": { 133 | ".read": false, 134 | ".write": false, 135 | "testData": { 136 | ".read": "auth != null", 137 | ".write": false, 138 | "toBeSet": { 139 | ".write": "auth != null" 140 | }, 141 | "toBeRemoved": { 142 | ".write": "auth != null" 143 | }, 144 | "toBeUpdated": { 145 | ".write": "auth != null" 146 | }, 147 | "sortedStrings": { 148 | ".indexOn": ".value" 149 | }, 150 | "sortedStringsWithPriorities": { 151 | ".indexOn": ".value", 152 | ".write": "auth != null" 153 | }, 154 | "dinosaurs": { 155 | ".indexOn": ["height", "length"] 156 | } 157 | } 158 | } 159 | }''' 160 | 161 | return gson.fromJson(rulesJson, FirebaseSecurityRules.class) 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/integration-test/groovy/org/restonfire/integrationTests/EventStreamTest.groovy: -------------------------------------------------------------------------------- 1 | package org.restonfire.integrationTests 2 | 3 | import org.jdeferred.ProgressCallback 4 | import org.jdeferred.Promise 5 | import org.restonfire.FirebaseRestEventStream 6 | import org.restonfire.FirebaseRestDatabase 7 | import org.restonfire.exceptions.FirebaseRuntimeException 8 | import org.restonfire.responses.StreamingEvent 9 | import org.restonfire.responses.StreamingEvent.EventType 10 | import spock.util.concurrent.AsyncConditions 11 | /** 12 | * Verifies streaming events against a real Firebase database. 13 | */ 14 | class EventStreamTest extends AbstractTest { 15 | 16 | private FirebaseRestDatabase namespace; 17 | 18 | void setup() { 19 | // Run this inside the setup to ensure that the setup function in the AbstractTest class is completed before the 20 | // namespace is crated. This ensures that the token will be created 21 | namespace = createNamespace() 22 | } 23 | 24 | def "Event stream - initial event"() { 25 | AsyncConditions progressCondition = new AsyncConditions(); 26 | AsyncConditions finalCondition = new AsyncConditions(); 27 | 28 | FirebaseRestEventStream eventStream = namespace.getEventStream("testData/text"); 29 | when: "starting to listen" 30 | 31 | eventStream 32 | .startListening() 33 | .progress(new ProgressCallback() { 34 | @Override 35 | void onProgress(StreamingEvent event) { 36 | 37 | progressCondition.evaluate { 38 | assert event.getEventType() == StreamingEvent.EventType.Set 39 | assert event.getEventData().getPath() == "/" 40 | assert event.getEventData().getData() == "aString" 41 | } 42 | } 43 | }) 44 | .always({ Promise.State state, Void val, FirebaseRuntimeException ex -> 45 | finalCondition.evaluate { 46 | assert ex == null 47 | assert val == null 48 | } 49 | }) 50 | then: "wait for result evaluation" 51 | progressCondition.await(10); 52 | 53 | eventStream.stopListening() 54 | finalCondition.await(10); 55 | } 56 | 57 | def "Event stream - event changing value type"() { 58 | AsyncConditions[] progressConditions = [ new AsyncConditions(), new AsyncConditions() ] 59 | AsyncConditions finalCondition = new AsyncConditions() 60 | int eventIndex = 0 61 | 62 | FirebaseRestEventStream eventStream = namespace.getEventStream("testData/toBeSet"); 63 | when: "starting to listen" 64 | 65 | eventStream 66 | .startListening() 67 | .progress(new ProgressCallback() { 68 | @Override 69 | void onProgress(StreamingEvent event) { 70 | 71 | if (eventIndex == 0) { 72 | progressConditions[eventIndex++].evaluate { 73 | assert event.getEventType() == EventType.Set 74 | assert event.getEventData().getPath() == "/" 75 | assert event.getEventData().getData() == "SET ME" 76 | 77 | // Change the type to an integer 78 | namespace.getReference("testData/toBeSet").setValue(5) 79 | } 80 | } else { 81 | progressConditions[eventIndex++].evaluate { 82 | assert event.getEventType() == EventType.Set 83 | assert event.getEventData().getPath() == "/" 84 | assert event.getEventData().getData() == 5.0 85 | } 86 | } 87 | } 88 | }) 89 | .always({ Promise.State state, Void val, FirebaseRuntimeException ex -> 90 | finalCondition.evaluate { 91 | assert ex == null 92 | assert val == null 93 | } 94 | }) 95 | then: "wait for result evaluation" 96 | progressConditions[0].await(10); 97 | progressConditions[1].await(10); 98 | 99 | eventStream.stopListening() 100 | finalCondition.await(10); 101 | } 102 | 103 | def "Event stream - event for update of partial value"() { 104 | AsyncConditions[] progressConditions = [ new AsyncConditions(), new AsyncConditions() ] 105 | AsyncConditions finalCondition = new AsyncConditions() 106 | int eventIndex = 0 107 | 108 | namespace.getReference("testData/toBeUpdated").updateValue([boo: "baz"]) 109 | 110 | FirebaseRestEventStream eventStream = namespace.getEventStream("testData/toBeUpdated"); 111 | when: "starting to listen" 112 | 113 | eventStream 114 | .startListening() 115 | .progress(new ProgressCallback() { 116 | @Override 117 | void onProgress(StreamingEvent event) { 118 | 119 | if (eventIndex == 0) { 120 | progressConditions[eventIndex++].evaluate { 121 | assert event.getEventType() == EventType.Set 122 | assert event.getEventData().getPath() == "/" 123 | assert event.getEventData().getData() == [foo: "bar", boo: "baz"] 124 | 125 | // Change the type to an integer 126 | namespace.getReference("testData/toBeUpdated").updateValue([foo: "foobar"]) 127 | } 128 | } else { 129 | progressConditions[eventIndex++].evaluate { 130 | assert event.getEventType() == EventType.Update 131 | assert event.getEventData().getPath() == "/" 132 | assert event.getEventData().getData() == [foo: "foobar"] 133 | } 134 | } 135 | } 136 | }) 137 | .always({ Promise.State state, Void val, FirebaseRuntimeException ex -> 138 | finalCondition.evaluate { 139 | assert ex == null 140 | assert val == null 141 | } 142 | }) 143 | then: "wait for result evaluation" 144 | progressConditions[0].await(10); 145 | progressConditions[1].await(10); 146 | 147 | eventStream.stopListening() 148 | finalCondition.await(10); 149 | } 150 | 151 | def "Event stream - event where child path was set"() { 152 | AsyncConditions[] progressConditions = [ new AsyncConditions(), new AsyncConditions() ] 153 | AsyncConditions finalCondition = new AsyncConditions() 154 | int eventIndex = 0 155 | 156 | namespace.getReference("testData/toBeUpdated").updateValue([boo: [biz: "baz"] ]) 157 | 158 | FirebaseRestEventStream eventStream = namespace.getEventStream("testData/toBeUpdated"); 159 | when: "starting to listen" 160 | 161 | eventStream 162 | .startListening() 163 | .progress(new ProgressCallback() { 164 | @Override 165 | void onProgress(StreamingEvent event) { 166 | 167 | if (eventIndex == 0) { 168 | progressConditions[eventIndex++].evaluate { 169 | assert event.getEventType() == EventType.Set 170 | assert event.getEventData().getPath() == "/" 171 | assert event.getEventData().getData() == [foo: "bar", boo: [biz: "baz"]] 172 | 173 | // Change the type to an integer 174 | namespace.getReference("testData/toBeUpdated/boo").setValue([biz: "Something New"]) 175 | } 176 | } else { 177 | progressConditions[eventIndex++].evaluate { 178 | assert event.getEventType() == EventType.Set 179 | assert event.getEventData().getPath() == "/boo" 180 | assert event.getEventData().getData() == [biz: "Something New"] 181 | } 182 | } 183 | } 184 | }) 185 | .always({ Promise.State state, Void val, FirebaseRuntimeException ex -> 186 | finalCondition.evaluate { 187 | assert ex == null 188 | assert val == null 189 | } 190 | }) 191 | then: "wait for result evaluation" 192 | progressConditions[0].await(10); 193 | progressConditions[1].await(10); 194 | 195 | eventStream.stopListening() 196 | finalCondition.await(10); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/integration-test/groovy/org/restonfire/integrationTests/FirebaseSecurityRulesTest.groovy: -------------------------------------------------------------------------------- 1 | package org.restonfire.integrationTests 2 | 3 | import com.google.gson.Gson 4 | import org.jdeferred.Promise 5 | import org.restonfire.FirebaseRestDatabase 6 | import org.restonfire.FirebaseSecurityRulesReference 7 | import org.restonfire.exceptions.FirebaseAccessException 8 | import org.restonfire.exceptions.FirebaseRuntimeException 9 | import org.restonfire.responses.FirebaseSecurityRules 10 | import org.slf4j.Logger 11 | import org.slf4j.LoggerFactory 12 | import spock.util.concurrent.AsyncConditions 13 | /** 14 | * Verifies security rules operations against a real Firebase namespace. 15 | */ 16 | class FirebaseSecurityRulesTest extends AbstractTest { 17 | 18 | static Logger LOG = LoggerFactory.getLogger(FirebaseSecurityRulesTest.class) 19 | 20 | private FirebaseRestDatabase namespaceWithToken; 21 | private FirebaseRestDatabase namespaceWithSecret; 22 | 23 | void setup() { 24 | // Run this inside the setup to ensure that the setup function in the AbstractTest class is completed before the 25 | // namespace is crated. This ensures that the token will be created 26 | namespaceWithToken = createNamespace() 27 | namespaceWithSecret = createNamespaceWithSecret() 28 | } 29 | 30 | def "Get current security rules without permission"() { 31 | AsyncConditions cond = new AsyncConditions(); 32 | 33 | when: "making request to retrieve the current security rules without permission" 34 | namespaceWithToken.getSecurityRules() 35 | .get() 36 | .always({ Promise.State state, FirebaseSecurityRules val, FirebaseRuntimeException ex -> 37 | cond.evaluate { 38 | assert val == null 39 | assert ex.getClass() == FirebaseAccessException.class 40 | } 41 | }) 42 | then: "wait for result evaluation" 43 | cond.await(5); 44 | } 45 | 46 | def "Get current security rules"() { 47 | AsyncConditions cond = new AsyncConditions(); 48 | 49 | when: "making request to retrieve the current security rules" 50 | namespaceWithSecret.getSecurityRules() 51 | .get() 52 | .always({ Promise.State state, FirebaseSecurityRules val, FirebaseRuntimeException ex -> 53 | cond.evaluate { 54 | assert ex == null 55 | assert val != null 56 | assert val.rules[FirebaseSecurityRules.READ_KEY] == false 57 | assert val.rules[FirebaseSecurityRules.WRITE_KEY] == false 58 | } 59 | }) 60 | then: "wait for result evaluation" 61 | cond.await(5); 62 | } 63 | 64 | def "Set security rules"() { 65 | AsyncConditions[] conditions = [new AsyncConditions(), new AsyncConditions(), new AsyncConditions()] 66 | int conditionIndex = 0 67 | 68 | FirebaseSecurityRulesReference ref = namespaceWithSecret.getSecurityRules() 69 | 70 | when: "making request to retrieve a string value without permission" 71 | 72 | ref 73 | .get() 74 | .always({ Promise.State getState, FirebaseSecurityRules getVal, FirebaseRuntimeException getEx -> 75 | conditions[conditionIndex++].evaluate { 76 | assert getEx == null 77 | assert getVal != null 78 | } 79 | 80 | def rulesMap = getVal.rules 81 | rulesMap[FirebaseSecurityRules.READ_KEY] = true 82 | 83 | def newRules = new FirebaseSecurityRules(rulesMap) 84 | 85 | LOG.info("New FB rulesMap: " + new Gson().toJson(getVal)) 86 | ref 87 | .set(newRules) 88 | .always({ Promise.State setState, FirebaseSecurityRules setVal, FirebaseRuntimeException setEx -> 89 | 90 | conditions[conditionIndex++].evaluate { 91 | assert setEx == null 92 | assert setVal == newRules 93 | } 94 | 95 | namespaceWithToken 96 | .getReference("noReadAccess") 97 | .getValue(String.class) 98 | .always({ Promise.State state, String val, FirebaseRuntimeException ex -> 99 | 100 | conditions[conditionIndex++].evaluate { 101 | assert getEx == null 102 | assert val == "BIG SECRET" 103 | } 104 | }) 105 | }) 106 | }) 107 | then: "wait for result evaluation" 108 | conditions[0].await(15); 109 | conditions[1].await(15); 110 | conditions[2].await(15); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/integration-test/groovy/org/restonfire/integrationTests/GetShallowValueOperationTest.groovy: -------------------------------------------------------------------------------- 1 | package org.restonfire.integrationTests 2 | 3 | import org.jdeferred.Promise 4 | import org.restonfire.FirebaseRestDatabase 5 | import org.restonfire.exceptions.FirebaseAccessException 6 | import org.restonfire.exceptions.FirebaseRuntimeException 7 | import spock.util.concurrent.AsyncConditions 8 | 9 | /** 10 | * Unit tests for retrieving shallow values. 11 | */ 12 | class GetShallowValueOperationTest extends AbstractTest { 13 | 14 | private FirebaseRestDatabase namespace; 15 | 16 | void setup() { 17 | // Run this inside the setup to ensure that the setup function in the AbstractTest class is completed before the 18 | // namespace is crated. This ensures that the token will be created 19 | namespace = createNamespace() 20 | } 21 | 22 | def "Try to get value without permission"() { 23 | AsyncConditions cond = new AsyncConditions(); 24 | 25 | when: "making request to retrieve a string value without permission" 26 | namespace.getReference("noReadAccess") 27 | .getShallowValue() 28 | .always({ Promise.State state, Object val, FirebaseRuntimeException ex -> 29 | cond.evaluate { 30 | assert val == null 31 | assert ex.getClass() == FirebaseAccessException.class 32 | } 33 | }) 34 | then: "wait for result evaluation" 35 | cond.await(3); 36 | } 37 | 38 | def "Get value for integer type"() { 39 | AsyncConditions cond = new AsyncConditions(); 40 | 41 | when: "making request to retrieve integer value" 42 | namespace.getReference("testData/integer") 43 | .getShallowValue() 44 | .always({ Promise.State state, Object val, FirebaseRuntimeException ex -> 45 | cond.evaluate { 46 | assert ex == null 47 | assert val == new Integer(1) 48 | } 49 | }) 50 | then: "wait for result evaluation" 51 | cond.await(3); 52 | } 53 | 54 | def "Get value for boolean type"() { 55 | AsyncConditions cond = new AsyncConditions(); 56 | 57 | when: "making request to retrieve integer value" 58 | namespace.getReference("testData/bool") 59 | .getShallowValue() 60 | .always({ Promise.State state, Object val, FirebaseRuntimeException ex -> 61 | cond.evaluate { 62 | assert ex == null 63 | assert val == true 64 | } 65 | }) 66 | then: "wait for result evaluation" 67 | cond.await(3); 68 | } 69 | 70 | def "Get value for string type"() { 71 | AsyncConditions cond = new AsyncConditions(); 72 | 73 | when: "making request to retrieve integer value" 74 | namespace.getReference("testData/text") 75 | .getShallowValue() 76 | .always({ Promise.State state, Object val, FirebaseRuntimeException ex -> 77 | cond.evaluate { 78 | assert ex == null 79 | assert val == "aString" 80 | } 81 | }) 82 | then: "wait for result evaluation" 83 | cond.await(3); 84 | } 85 | 86 | def "Get shallow value for map of objects"() { 87 | AsyncConditions cond = new AsyncConditions(); 88 | 89 | when: "making request to retrieve integer value" 90 | namespace.getReference("testData/dinosaurs") 91 | .getShallowValue() 92 | .always({ Promise.State state, Object val, FirebaseRuntimeException ex -> 93 | cond.evaluate { 94 | assert ex == null 95 | 96 | def expectedKeysInOrder = [ "lambeosaurus", "stegosaurus" ] as Queue 97 | ((Map)val).each{ k, v -> 98 | assert k == expectedKeysInOrder.poll(); 99 | assert v == true 100 | } 101 | } 102 | }) 103 | then: "wait for result evaluation" 104 | cond.await(3); 105 | } 106 | 107 | def "Get shallow value for map of strings"() { 108 | AsyncConditions cond = new AsyncConditions(); 109 | 110 | when: "making request to retrieve integer value" 111 | namespace.getReference("testData/sortedStrings") 112 | .getShallowValue() 113 | .always({ Promise.State state, Object val, FirebaseRuntimeException ex -> 114 | cond.evaluate { 115 | assert ex == null 116 | 117 | def expectedKeysInOrder = [ "a", "b", "c" ] as Queue 118 | ((Map)val).each{ k, v -> 119 | assert k == expectedKeysInOrder.poll(); 120 | assert v == true 121 | } 122 | } 123 | }) 124 | then: "wait for result evaluation" 125 | cond.await(3); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/integration-test/groovy/org/restonfire/integrationTests/GetValueOperationTest.groovy: -------------------------------------------------------------------------------- 1 | package org.restonfire.integrationTests 2 | 3 | import org.jdeferred.DoneCallback 4 | import org.jdeferred.Promise 5 | import org.restonfire.FirebaseRestDatabase 6 | import org.restonfire.exceptions.FirebaseAccessException 7 | import org.restonfire.exceptions.FirebaseRuntimeException 8 | import org.restonfire.integrationTests.data.SampleData 9 | import spock.util.concurrent.AsyncConditions 10 | 11 | /** 12 | * Verifies get operations against a real Firebase namespace. 13 | */ 14 | class GetValueOperationTest extends AbstractTest { 15 | 16 | private FirebaseRestDatabase namespace; 17 | 18 | void setup() { 19 | // Run this inside the setup to ensure that the setup function in the AbstractTest class is completed before the 20 | // namespace is crated. This ensures that the token will be created 21 | namespace = createNamespace() 22 | } 23 | 24 | def "Try to get value without permission"() { 25 | AsyncConditions cond = new AsyncConditions(); 26 | 27 | when: "making request to retrieve a string value without permission" 28 | namespace.getReference("noReadAccess") 29 | .getValue(String.class) 30 | .always({ Promise.State state, String val, FirebaseRuntimeException ex -> 31 | cond.evaluate { 32 | assert val == null 33 | assert ex.getClass() == FirebaseAccessException.class 34 | } 35 | }) 36 | then: "wait for result evaluation" 37 | cond.await(3); 38 | } 39 | 40 | def "Get value for integer type"() { 41 | AsyncConditions cond = new AsyncConditions(); 42 | 43 | when: "making request to retrieve integer value" 44 | namespace.getReference("testData/integer") 45 | .getValue(Integer.class) 46 | .always({ Promise.State state, Integer val, FirebaseRuntimeException ex -> 47 | cond.evaluate { 48 | assert ex == null 49 | assert val == 1 50 | } 51 | }) 52 | then: "wait for result evaluation" 53 | cond.await(3); 54 | } 55 | 56 | def "Get value for boolean type"() { 57 | AsyncConditions cond = new AsyncConditions(); 58 | 59 | when: "making request to retrieve integer value" 60 | namespace.getReference("testData/bool") 61 | .getValue(Boolean.class) 62 | .always({ Promise.State state, Boolean val, FirebaseRuntimeException ex -> 63 | cond.evaluate { 64 | assert ex == null 65 | assert val 66 | } 67 | }) 68 | then: "wait for result evaluation" 69 | cond.await(3); 70 | } 71 | 72 | def "Get value for string type"() { 73 | AsyncConditions cond = new AsyncConditions(); 74 | 75 | when: "making request to retrieve integer value" 76 | namespace.getReference("testData/text") 77 | .getValue(String.class) 78 | .always({ Promise.State state, String val, FirebaseRuntimeException ex -> 79 | cond.evaluate { 80 | assert ex == null 81 | assert val == "aString" 82 | } 83 | }) 84 | then: "wait for result evaluation" 85 | cond.await(3); 86 | } 87 | 88 | def "Get value for string type with done callback"() { 89 | AsyncConditions cond = new AsyncConditions(); 90 | 91 | when: "making request to retrieve integer value" 92 | namespace.getReference("testData/text") 93 | .getValue(String.class) 94 | .done(new DoneCallback() { 95 | @Override 96 | void onDone(String result) { 97 | cond.evaluate { 98 | assert result == "aString" 99 | } 100 | } 101 | }) 102 | then: "wait for result evaluation" 103 | cond.await(3); 104 | } 105 | 106 | def "Get value for SampleData type"() { 107 | AsyncConditions cond = new AsyncConditions(); 108 | 109 | when: "making request to retrieve integer value" 110 | namespace.getReference("testData/sampleData") 111 | .getValue(SampleData.class) 112 | .always({ Promise.State state, SampleData val, FirebaseRuntimeException ex -> 113 | cond.evaluate { 114 | assert ex == null 115 | assert val != null 116 | assert val.foo == "bar" 117 | } 118 | }) 119 | then: "wait for result evaluation" 120 | cond.await(3); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/integration-test/groovy/org/restonfire/integrationTests/OrderByValueOperationTest.groovy: -------------------------------------------------------------------------------- 1 | package org.restonfire.integrationTests 2 | 3 | import org.jdeferred.Promise 4 | import org.restonfire.FirebaseRestDatabase 5 | import org.restonfire.exceptions.FirebaseAccessException 6 | import org.restonfire.exceptions.FirebaseRuntimeException 7 | import spock.util.concurrent.AsyncConditions 8 | 9 | /** 10 | * Verifies ordered get operations against a real Firebase namespace. 11 | */ 12 | class OrderByValueOperationTest extends AbstractTest { 13 | 14 | private FirebaseRestDatabase namespace; 15 | 16 | void setup() { 17 | // Run this inside the setup to ensure that the setup function in the AbstractTest class is completed before the 18 | // namespace is crated. This ensures that the token will be created 19 | namespace = createNamespace() 20 | } 21 | 22 | def "Try to get ordered value without permission"() { 23 | AsyncConditions cond = new AsyncConditions(); 24 | 25 | when: "making request to retrieve a string values without permission" 26 | namespace.getReference("noReadAccess") 27 | .query() 28 | .orderByValue() 29 | .run(Map.class) 30 | .always({ Promise.State state, String val, FirebaseRuntimeException ex -> 31 | cond.evaluate { 32 | assert val == null 33 | assert ex.getClass() == FirebaseAccessException.class 34 | } 35 | }) 36 | then: "wait for result evaluation" 37 | cond.await(5); 38 | } 39 | 40 | def "Order by value without filter"() { 41 | AsyncConditions cond = new AsyncConditions(); 42 | 43 | when: "querying for records" 44 | namespace.getReference("testData/sortedStrings") 45 | .query() 46 | .orderByValue() 47 | .run(Map.class) 48 | .always({ Promise.State state, Map val, FirebaseRuntimeException ex -> 49 | cond.evaluate { 50 | assert ex == null 51 | 52 | def expectedValuesInOrder = [ "x", "z", "y"] as Queue 53 | val.each{ k, v -> assert v == expectedValuesInOrder.poll() } 54 | } 55 | }) 56 | then: "wait for result evaluation" 57 | cond.await(3); 58 | } 59 | 60 | def "Order by value with start at"() { 61 | AsyncConditions cond = new AsyncConditions(); 62 | 63 | when: "querying for records" 64 | namespace.getReference("testData/sortedStrings") 65 | .query() 66 | .orderByValue() 67 | .startAt("y") 68 | .run(Map.class) 69 | .always({ Promise.State state, Map val, FirebaseRuntimeException ex -> 70 | cond.evaluate { 71 | assert ex == null 72 | 73 | def expectedValuesInOrder = [ "y", "z" ] as Queue 74 | val.each{ k, v -> assert v == expectedValuesInOrder.poll() } 75 | } 76 | }) 77 | then: "wait for result evaluation" 78 | cond.await(3); 79 | } 80 | 81 | def "Order by value with end at"() { 82 | AsyncConditions cond = new AsyncConditions(); 83 | 84 | when: "querying for records" 85 | namespace.getReference("testData/sortedStrings") 86 | .query() 87 | .orderByValue() 88 | .endAt("y") 89 | .run(Map.class) 90 | .always({ Promise.State state, Map val, FirebaseRuntimeException ex -> 91 | cond.evaluate { 92 | assert ex == null 93 | 94 | def expectedValuesInOrder = [ "x", "y" ] as Queue 95 | val.each{ k, v -> assert v == expectedValuesInOrder.poll() } 96 | } 97 | }) 98 | then: "wait for result evaluation" 99 | cond.await(3); 100 | } 101 | 102 | def "Order by value limit to first"() { 103 | AsyncConditions cond = new AsyncConditions(); 104 | 105 | when: "querying for records" 106 | namespace.getReference("testData/sortedStrings") 107 | .query() 108 | .orderByValue() 109 | .limitToFirst(2) 110 | .run(Map.class) 111 | .always({ Promise.State state, Map val, FirebaseRuntimeException ex -> 112 | cond.evaluate { 113 | assert ex == null 114 | 115 | def expectedValuesInOrder = [ "x", "y" ] as Queue 116 | val.each{ k, v -> assert v == expectedValuesInOrder.poll() } 117 | } 118 | }) 119 | then: "wait for result evaluation" 120 | cond.await(3); 121 | } 122 | 123 | def "Order by value limit to last"() { 124 | AsyncConditions cond = new AsyncConditions(); 125 | 126 | when: "querying for records" 127 | namespace.getReference("testData/sortedStrings") 128 | .query() 129 | .orderByValue() 130 | .limitToLast(1) 131 | .run(Map.class) 132 | .always({ Promise.State state, Map val, FirebaseRuntimeException ex -> 133 | cond.evaluate { 134 | assert ex == null 135 | 136 | def expectedValuesInOrder = [ "z" ] as Queue 137 | val.each{ k, v -> assert v == expectedValuesInOrder.poll() } 138 | } 139 | }) 140 | then: "wait for result evaluation" 141 | cond.await(3); 142 | } 143 | 144 | def "Order by value with start and end"() { 145 | AsyncConditions cond = new AsyncConditions(); 146 | 147 | when: "querying for records" 148 | namespace.getReference("testData/sortedStrings") 149 | .query() 150 | .orderByValue() 151 | .startAt("y") 152 | .endAt("y") 153 | .run(Map.class) 154 | .always({ Promise.State state, Map val, FirebaseRuntimeException ex -> 155 | cond.evaluate { 156 | assert ex == null 157 | 158 | def expectedValuesInOrder = [ "y" ] as Queue 159 | val.each{ k, v -> assert v == expectedValuesInOrder.poll() } 160 | } 161 | }) 162 | then: "wait for result evaluation" 163 | cond.await(3); 164 | } 165 | 166 | def "Order by value with equal filter"() { 167 | AsyncConditions cond = new AsyncConditions(); 168 | 169 | when: "querying for records" 170 | namespace.getReference("testData/sortedStrings") 171 | .query() 172 | .orderByValue() 173 | .equalTo("y") 174 | .run(Map.class) 175 | .always({ Promise.State state, Map val, FirebaseRuntimeException ex -> 176 | cond.evaluate { 177 | assert ex == null 178 | 179 | def expectedValuesInOrder = [ "y" ] as Queue 180 | val.each{ k, v -> assert v == expectedValuesInOrder.poll() } 181 | } 182 | }) 183 | then: "wait for result evaluation" 184 | cond.await(3); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/integration-test/groovy/org/restonfire/integrationTests/OtherOrderByOperationTest.groovy: -------------------------------------------------------------------------------- 1 | package org.restonfire.integrationTests 2 | 3 | import org.jdeferred.DeferredManager 4 | import org.jdeferred.DoneCallback 5 | import org.jdeferred.Promise 6 | import org.jdeferred.impl.DefaultDeferredManager 7 | import org.jdeferred.multiple.MultipleResults 8 | import org.restonfire.FirebaseRestDatabase 9 | import org.restonfire.exceptions.FirebaseRuntimeException 10 | import spock.util.concurrent.AsyncConditions 11 | /** 12 | * Tests for various queries requireing the order-by clause. 13 | */ 14 | class OtherOrderByOperationTest extends AbstractTest { 15 | 16 | private FirebaseRestDatabase namespace; 17 | 18 | void setup() { 19 | // Run this inside the setup to ensure that the setup function in the AbstractTest class is completed before the 20 | // namespace is crated. This ensures that the token will be created 21 | namespace = createNamespace() 22 | } 23 | 24 | def "Get ordered key without filter"() { 25 | AsyncConditions cond = new AsyncConditions(); 26 | 27 | when: "querying for records" 28 | namespace.getReference("testData/sortedStrings") 29 | .query() 30 | .orderByKey() 31 | .run(Map.class) 32 | .always({ Promise.State state, Map val, FirebaseRuntimeException ex -> 33 | cond.evaluate { 34 | assert ex == null 35 | 36 | def expectedValuesInOrder = [ "a", "b", "c"] as Queue 37 | val.each{ k, v -> assert k == expectedValuesInOrder.poll() } 38 | } 39 | }) 40 | then: "wait for result evaluation" 41 | cond.await(3); 42 | } 43 | 44 | def "Order by key with start at"() { 45 | AsyncConditions cond = new AsyncConditions(); 46 | 47 | when: "querying for records" 48 | namespace.getReference("testData/sortedStrings") 49 | .query() 50 | .orderByKey() 51 | .startAt("b") 52 | .run(Map.class) 53 | .always({ Promise.State state, Map val, FirebaseRuntimeException ex -> 54 | cond.evaluate { 55 | assert ex == null 56 | 57 | def expectedValuesInOrder = [ "b", "c" ] as Queue 58 | val.each{ k, v -> assert k == expectedValuesInOrder.poll() } 59 | } 60 | }) 61 | then: "wait for result evaluation" 62 | cond.await(3); 63 | } 64 | 65 | def "Order by child with end at"() { 66 | AsyncConditions cond = new AsyncConditions(); 67 | 68 | when: "querying for records" 69 | namespace.getReference("testData/dinosaurs") 70 | .query() 71 | .orderByChild("length") 72 | .endAt("z") 73 | .run(Map.class) 74 | .always({ Promise.State state, Map val, FirebaseRuntimeException ex -> 75 | cond.evaluate { 76 | assert ex == null 77 | 78 | def expectedValuesInOrder = [ "stegosaurus", "lambeosaurus" ] as Queue 79 | val.each{ k, v -> assert k == expectedValuesInOrder.poll() } 80 | } 81 | }) 82 | then: "wait for result evaluation" 83 | cond.await(3); 84 | } 85 | 86 | def "Order by priority with end at"() { 87 | AsyncConditions cond = new AsyncConditions(); 88 | 89 | when: "querying for records" 90 | namespace.getReference("testData/sortedStringsWithPriorities") 91 | .query() 92 | .orderByPriority() 93 | .endAt(2.9) 94 | .run(Map.class) 95 | .always({ Promise.State state, Map val, FirebaseRuntimeException ex -> 96 | cond.evaluate { 97 | assert ex == null 98 | 99 | def expectedValuesInOrder = [ "c", "b" ] as Queue 100 | val.each{ k, v -> assert k == expectedValuesInOrder.poll() } 101 | } 102 | }) 103 | then: "wait for result evaluation" 104 | cond.await(5); 105 | } 106 | 107 | def "Order by priority after overriding it"() { 108 | AsyncConditions cond = new AsyncConditions(); 109 | 110 | DeferredManager deferredManager = new DefaultDeferredManager(); 111 | 112 | def sortedStringsRef = namespace.getReference("testData/sortedStringsWithPriorities") 113 | 114 | def p1 = sortedStringsRef.child("c").setPriority(1.0); 115 | def p2 = sortedStringsRef.child("a").setPriority(2.0); 116 | def p3 = sortedStringsRef.child("b").setPriority(3.0); 117 | 118 | when: "querying for records" 119 | 120 | deferredManager.when(p1, p2, p3) 121 | .done(new DoneCallback() { 122 | @Override 123 | void onDone(MultipleResults result) { 124 | 125 | sortedStringsRef 126 | .query() 127 | .orderByPriority() 128 | .startAt(1.0) 129 | .run(Map.class) 130 | .always({ Promise.State state, Map val, FirebaseRuntimeException ex -> 131 | cond.evaluate { 132 | assert ex == null 133 | 134 | def expectedValuesInOrder = [ "c", "a", "b" ] as Queue 135 | val.each{ k, v -> assert k == expectedValuesInOrder.poll() } 136 | } 137 | }) 138 | } 139 | }) 140 | 141 | then: "wait for result evaluation" 142 | cond.await(10) 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/integration-test/groovy/org/restonfire/integrationTests/PriorityOperationTest.groovy: -------------------------------------------------------------------------------- 1 | package org.restonfire.integrationTests 2 | 3 | import org.jdeferred.Promise 4 | import org.restonfire.FirebaseRestDatabase 5 | import org.restonfire.exceptions.FirebaseAccessException 6 | import org.restonfire.exceptions.FirebaseRuntimeException 7 | import spock.util.concurrent.AsyncConditions 8 | /** 9 | * Verifies operations with priorities against a real Firebase database. 10 | */ 11 | class PriorityOperationTest extends AbstractTest { 12 | 13 | private FirebaseRestDatabase namespace; 14 | 15 | void setup() { 16 | // Run this inside the setup to ensure that the setup function in the AbstractTest class is completed before the 17 | // namespace is crated. This ensures that the token will be created 18 | namespace = createNamespace() 19 | } 20 | 21 | def "Try to get priority without permission"() { 22 | AsyncConditions cond = new AsyncConditions(); 23 | 24 | when: "making request to get priority without permission" 25 | namespace.getReference("noReadAccess") 26 | .getPriority() 27 | .always({ Promise.State state, String val, FirebaseRuntimeException ex -> 28 | cond.evaluate { 29 | assert val == null 30 | assert ex.getClass() == FirebaseAccessException.class 31 | } 32 | }) 33 | then: "wait for result evaluation" 34 | cond.await(3); 35 | } 36 | 37 | def "Get priority for value without a priority"() { 38 | AsyncConditions cond = new AsyncConditions(); 39 | 40 | when: "making request to get priority" 41 | namespace.getReference("testData/text") 42 | .getPriority() 43 | .always({ Promise.State state, Double val, FirebaseRuntimeException ex -> 44 | cond.evaluate { 45 | assert ex == null 46 | assert val == null 47 | } 48 | }) 49 | then: "wait for result evaluation" 50 | cond.await(3); 51 | } 52 | 53 | def "Set priority for value"() { 54 | AsyncConditions cond = new AsyncConditions(); 55 | 56 | when: "making request to set priority" 57 | def ref = namespace.getReference("testData/toBeSet") 58 | 59 | ref 60 | .setPriority(3.0) 61 | .always({ Promise.State setState, Void val, FirebaseRuntimeException setEx -> 62 | def getValPromise = ref.getPriority() 63 | 64 | getValPromise.always { Promise.State getState, Double getResult, FirebaseRuntimeException getEx -> 65 | cond.evaluate { 66 | assert setEx == null 67 | 68 | assert getEx == null 69 | assert getResult == 3.0 70 | } 71 | } 72 | }) 73 | then: "wait for result evaluation" 74 | cond.await(5); 75 | } 76 | 77 | def "Remove priority for value"() { 78 | AsyncConditions cond = new AsyncConditions(); 79 | 80 | when: "making request to set priority" 81 | def ref = namespace.getReference("testData/sortedStringsWithPriorities/b") 82 | 83 | ref 84 | .removePriority() 85 | .always({ Promise.State setState, Void val, FirebaseRuntimeException setEx -> 86 | def getValPromise = ref.getPriority() 87 | 88 | getValPromise.always { Promise.State getState, Double getResult, FirebaseRuntimeException getEx -> 89 | cond.evaluate { 90 | assert setEx == null 91 | 92 | assert getEx == null 93 | assert getResult == null 94 | } 95 | } 96 | }) 97 | then: "wait for result evaluation" 98 | cond.await(5); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/integration-test/groovy/org/restonfire/integrationTests/RemoveValueOperationTest.groovy: -------------------------------------------------------------------------------- 1 | package org.restonfire.integrationTests 2 | 3 | import org.jdeferred.Promise 4 | import org.restonfire.FirebaseRestDatabase 5 | import org.restonfire.exceptions.FirebaseAccessException 6 | import org.restonfire.exceptions.FirebaseRuntimeException 7 | import spock.util.concurrent.AsyncConditions 8 | 9 | /** 10 | * Verifies removal operations against a real Firebase namespace. 11 | */ 12 | class RemoveValueOperationTest extends AbstractTest { 13 | 14 | private FirebaseRestDatabase namespace; 15 | 16 | void setup() { 17 | // Run this inside the setup to ensure that the setup function in the AbstractTest class is completed before the 18 | // namespace is crated. This ensures that the token will be created 19 | namespace = createNamespace() 20 | } 21 | 22 | def "Try to remove value without permission"() { 23 | AsyncConditions cond = new AsyncConditions(); 24 | 25 | when: "making request to remove a string value without permission" 26 | namespace.getReference("testData/text") 27 | .removeValue() 28 | .always({ Promise.State state, Void val, FirebaseRuntimeException ex -> 29 | cond.evaluate { 30 | assert val == null 31 | assert ex.getClass() == FirebaseAccessException.class 32 | } 33 | }) 34 | then: "wait for result evaluation" 35 | cond.await(3); 36 | } 37 | 38 | def "Remove value"() { 39 | AsyncConditions cond = new AsyncConditions(); 40 | 41 | when: "making request to remove value" 42 | def ref = namespace.getReference("testData/toBeRemoved") 43 | 44 | ref 45 | .removeValue() 46 | .always({ Promise.State setState, Void setResult, FirebaseRuntimeException setEx -> 47 | def getValPromise = ref.getValue(String.class) 48 | 49 | getValPromise.always { Promise.State getState, String getResult, FirebaseRuntimeException getEx -> 50 | cond.evaluate { 51 | assert setResult == null 52 | assert setEx == null 53 | 54 | assert getEx == null 55 | assert getResult == null 56 | } 57 | } 58 | }) 59 | then: "wait for result evaluation" 60 | cond.await(3); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/integration-test/groovy/org/restonfire/integrationTests/SetValueOperationTest.groovy: -------------------------------------------------------------------------------- 1 | package org.restonfire.integrationTests 2 | 3 | import org.jdeferred.Promise 4 | import org.restonfire.FirebaseRestDatabase 5 | import org.restonfire.exceptions.FirebaseAccessException 6 | import org.restonfire.exceptions.FirebaseRuntimeException 7 | import org.restonfire.integrationTests.data.SampleData 8 | import spock.util.concurrent.AsyncConditions 9 | 10 | /** 11 | * Verifies set operations against a real Firebase namespace. 12 | */ 13 | class SetValueOperationTest extends AbstractTest { 14 | 15 | private FirebaseRestDatabase namespace; 16 | 17 | void setup() { 18 | // Run this inside the setup to ensure that the setup function in the AbstractTest class is completed before the 19 | // namespace is crated. This ensures that the token will be created 20 | namespace = createNamespace() 21 | } 22 | 23 | def "Try to set value without permission"() { 24 | AsyncConditions cond = new AsyncConditions(); 25 | 26 | when: "making request to set the integer value" 27 | def ref = namespace.getReference("testData/integer") 28 | 29 | ref 30 | .setValue(4) 31 | .always({ Promise.State setState, Integer setResult, FirebaseRuntimeException setEx -> 32 | def getValPromise = ref.getValue(Integer.class) 33 | 34 | getValPromise.always { Promise.State getState, Integer getResult, FirebaseRuntimeException getEx -> 35 | cond.evaluate { 36 | assert setResult == null 37 | assert setEx.getClass() == FirebaseAccessException.class 38 | 39 | assert getEx == null 40 | assert getResult == 1 41 | } 42 | } 43 | }) 44 | 45 | then: "wait for result evaluation" 46 | cond.await(5); 47 | } 48 | 49 | def "Set value for integer type"() { 50 | AsyncConditions cond = new AsyncConditions(); 51 | 52 | when: "making request to set integer value" 53 | def ref = namespace.getReference("testData/toBeSet") 54 | 55 | ref 56 | .setValue(2) 57 | .always({ Promise.State setState, Integer setResult, FirebaseRuntimeException setEx -> 58 | def getValPromise = ref.getValue(Integer.class) 59 | 60 | getValPromise.always { Promise.State getState, Integer getResult, FirebaseRuntimeException getEx -> 61 | cond.evaluate { 62 | assert setEx == null 63 | assert setResult == 2 64 | 65 | assert getEx == null 66 | assert getResult == 2 67 | } 68 | } 69 | }) 70 | then: "wait for result evaluation" 71 | cond.await(5); 72 | } 73 | 74 | def "Set value for String type"() { 75 | AsyncConditions cond = new AsyncConditions(); 76 | 77 | when: "making request to set string value" 78 | def ref = namespace.getReference("testData/toBeSet") 79 | 80 | def newValue = "some different string" 81 | ref 82 | .setValue(newValue) 83 | .always({ Promise.State setState, String setResult, FirebaseRuntimeException setEx -> 84 | def getValPromise = ref.getValue(String.class) 85 | 86 | getValPromise.always { Promise.State getState, String getResult, FirebaseRuntimeException getEx -> 87 | cond.evaluate { 88 | assert setEx == null 89 | assert setResult == newValue 90 | 91 | assert getEx == null 92 | assert getResult == newValue 93 | } 94 | } 95 | }) 96 | then: "wait for result evaluation" 97 | cond.await(5); 98 | } 99 | 100 | def "Set value for SampleData type"() { 101 | AsyncConditions cond = new AsyncConditions(); 102 | 103 | when: "making request to set string value" 104 | def ref = namespace.getReference("testData/toBeSet") 105 | 106 | def newValue = new SampleData(foo: "new bar") 107 | ref 108 | .setValue(newValue) 109 | .always({ Promise.State setState, SampleData setResult, FirebaseRuntimeException setEx -> 110 | def getValPromise = ref.getValue(SampleData.class) 111 | 112 | getValPromise.always { Promise.State getState, SampleData getResult, FirebaseRuntimeException getEx -> 113 | cond.evaluate { 114 | assert setEx == null 115 | assert setResult.foo == "new bar" 116 | 117 | assert getEx == null 118 | assert getResult.foo == "new bar" 119 | } 120 | } 121 | }) 122 | then: "wait for result evaluation" 123 | cond.await(5); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/integration-test/groovy/org/restonfire/integrationTests/UpdateValueOperationTest.groovy: -------------------------------------------------------------------------------- 1 | package org.restonfire.integrationTests 2 | 3 | import org.jdeferred.Promise 4 | import org.restonfire.FirebaseRestDatabase 5 | import org.restonfire.exceptions.FirebaseAccessException 6 | import org.restonfire.exceptions.FirebaseRuntimeException 7 | import spock.util.concurrent.AsyncConditions 8 | /** 9 | * Verifies update operations against a real Firebase namespace. 10 | */ 11 | class UpdateValueOperationTest extends AbstractTest { 12 | private FirebaseRestDatabase namespace; 13 | 14 | void setup() { 15 | // Run this inside the setup to ensure that the setup function in the AbstractTest class is completed before the 16 | // namespace is crated. This ensures that the token will be created 17 | namespace = createNamespace() 18 | } 19 | 20 | def "Try to update value without permission"() { 21 | AsyncConditions cond = new AsyncConditions(); 22 | 23 | when: "making request to set the integer value" 24 | def ref = namespace.getReference("testData/sampleData") 25 | 26 | ref 27 | .updateValue([ foo: "newVal" ]) 28 | .always({ Promise.State setState, Map setResult, FirebaseRuntimeException setEx -> 29 | def getValPromise = ref.getValue(Map.class) 30 | 31 | getValPromise.always { Promise.State getState, Map getResult, FirebaseRuntimeException getEx -> 32 | cond.evaluate { 33 | assert setResult == null 34 | assert setEx.getClass() == FirebaseAccessException.class 35 | 36 | assert getEx == null 37 | assert getResult == [foo: "bar"] 38 | } 39 | } 40 | }) 41 | 42 | then: "wait for result evaluation" 43 | cond.await(5); 44 | } 45 | 46 | def "Update value with new property"() { 47 | AsyncConditions cond = new AsyncConditions(); 48 | 49 | when: "making request to update value by adding new property" 50 | def ref = namespace.getReference("testData/toBeUpdated") 51 | 52 | ref 53 | .updateValue([ newInt: 10 ]) 54 | .always({ Promise.State setState, Map setResult, FirebaseRuntimeException setEx -> 55 | def getValPromise = ref.getValue(Map.class) 56 | 57 | getValPromise.always { Promise.State getState, Map getResult, FirebaseRuntimeException getEx -> 58 | cond.evaluate { 59 | assert setEx == null 60 | assert setResult == [ newInt: 10 ] 61 | 62 | assert getEx == null 63 | assert getResult == [ newInt: 10, foo: "bar" ] 64 | } 65 | } 66 | }) 67 | then: "wait for result evaluation" 68 | cond.await(5); 69 | } 70 | 71 | def "Update value existing property"() { 72 | AsyncConditions cond = new AsyncConditions(); 73 | 74 | when: "making request to update value by updating existing property" 75 | def ref = namespace.getReference("testData/toBeUpdated") 76 | 77 | ref 78 | .updateValue([ foo: "another bar" ]) 79 | .always({ Promise.State setState, Map setResult, FirebaseRuntimeException setEx -> 80 | def getValPromise = ref.getValue(Map.class) 81 | 82 | getValPromise.always { Promise.State getState, Map getResult, FirebaseRuntimeException getEx -> 83 | cond.evaluate { 84 | assert setEx == null 85 | assert setResult == [ foo: "another bar" ] 86 | 87 | assert getEx == null 88 | assert getResult == [ foo: "another bar" ] 89 | } 90 | } 91 | }) 92 | then: "wait for result evaluation" 93 | cond.await(5); 94 | } 95 | 96 | def "Update value removing property"() { 97 | AsyncConditions cond = new AsyncConditions(); 98 | 99 | when: "making request with null value to be ignored" 100 | def ref = namespace.getReference("testData/toBeUpdated") 101 | 102 | ref 103 | .updateValue([ foo: null, newProp: "bar" ]) 104 | .always({ Promise.State setState, Map setResult, FirebaseRuntimeException setEx -> 105 | def getValPromise = ref.getValue(Map.class) 106 | 107 | getValPromise.always { Promise.State getState, Map getResult, FirebaseRuntimeException getEx -> 108 | cond.evaluate { 109 | assert setEx == null 110 | assert setResult == [ foo: null, newProp: "bar" ] 111 | 112 | assert getEx == null 113 | assert getResult == [ foo: "bar", newProp: "bar" ] 114 | } 115 | } 116 | }) 117 | then: "wait for result evaluation" 118 | cond.await(5); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/integration-test/groovy/org/restonfire/integrationTests/data/SampleData.java: -------------------------------------------------------------------------------- 1 | package org.restonfire.integrationTests.data; 2 | 3 | /** 4 | * Pojo for sample data of the integration tests. 5 | */ 6 | public class SampleData { 7 | String foo; 8 | } 9 | -------------------------------------------------------------------------------- /src/integration-test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | UTF-8 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/integration-test/resources/org/restonfire/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j-fischer/rest-on-fire/2e264b14ed0cbff156d2a625b139fc7c2df4bd56/src/integration-test/resources/org/restonfire/.gitkeep -------------------------------------------------------------------------------- /src/main/java/org/restonfire/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j-fischer/rest-on-fire/2e264b14ed0cbff156d2a625b139fc7c2df4bd56/src/main/java/org/restonfire/.gitkeep -------------------------------------------------------------------------------- /src/main/java/org/restonfire/BaseFirebaseRestDatabaseFactory.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | import com.google.gson.Gson; 4 | import com.ning.http.client.AsyncHttpClient; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | /** 9 | * {@link FirebaseRestDatabaseFactory} implementation using {@link AsyncHttpClient} as the HTTP 10 | * transport library. This factory also requires the Firebase access token to be provided 11 | * for the creation. 12 | */ 13 | public final class BaseFirebaseRestDatabaseFactory implements FirebaseRestDatabaseFactory { 14 | 15 | private static final Logger LOG = LoggerFactory.getLogger(BaseFirebaseRestDatabaseFactory.class); 16 | 17 | private final AsyncHttpClient asyncHttpClient; 18 | private final Gson gson; 19 | 20 | /** 21 | * Base factory which requires the {@link AsyncHttpClient} and {@link Gson} dependencies to be injected. 22 | * 23 | * @param asyncHttpClient {@link AsyncHttpClient} instance, which will be used for the HTTP requests. 24 | * @param gson {@link Gson} instance used for deserialization of all reference responses. 25 | */ 26 | public BaseFirebaseRestDatabaseFactory( 27 | AsyncHttpClient asyncHttpClient, 28 | Gson gson 29 | ) { 30 | this.asyncHttpClient = asyncHttpClient; 31 | this.gson = gson; 32 | } 33 | 34 | @Override 35 | public FirebaseRestDatabase create( 36 | String databaseUrl, 37 | String firebaseAccessToken) { 38 | 39 | LOG.info("Creating FirebaseRestDatabase for url '{}' {} accessToken", 40 | databaseUrl, 41 | StringUtil.notNullOrEmpty(firebaseAccessToken) ? "with" : "without" 42 | ); 43 | 44 | return new FirebaseRestDatabaseImpl(asyncHttpClient, gson, databaseUrl, firebaseAccessToken); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/FirebaseDocumentLocation.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | /** 4 | * Created by jfischer on 2016-05-07. 5 | */ 6 | abstract class FirebaseDocumentLocation { 7 | 8 | public static final String JSON_SUFFIX = ".json"; 9 | 10 | protected final String path; 11 | protected final String fbBaseUrl; 12 | protected final String fbAccessToken; 13 | protected final String referenceUrl; 14 | 15 | FirebaseDocumentLocation(String fbBaseUrl, String path, String fbAccessToken) { 16 | this.fbBaseUrl = fbBaseUrl; 17 | this.path = path; 18 | this.fbAccessToken = fbAccessToken; 19 | 20 | this.referenceUrl = PathUtil.concatenatePath(fbBaseUrl, path) + JSON_SUFFIX; 21 | } 22 | 23 | public String getReferenceUrl() { 24 | return referenceUrl.substring(0, referenceUrl.length() - JSON_SUFFIX.length()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/FirebaseRestDatabase.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | /** 4 | * A {@link FirebaseRestDatabase} is a base representation of a Firebase database. It 5 | * manages all resources for the {@link FirebaseRestReference} implementations as well as 6 | * handles authentication for that database. 7 | */ 8 | public interface FirebaseRestDatabase { 9 | 10 | /** 11 | * Returns a {@link FirebaseRestReference} object representing the provided location of the database. 12 | * With this reference object, the consumer can read or modify the data at this location, or travers 13 | * further down or up the tree. 14 | * 15 | * @param path The location within database to create a reference for. 16 | * @return The {@link FirebaseRestReference} for the given path. 17 | */ 18 | FirebaseRestReference getReference(String path); 19 | 20 | /** 21 | * Returns a {@link FirebaseRestEventStream} object representing the provided location of the namespace. 22 | * With this reference object, the consumer can listen to changes for the data at this location, or travers 23 | * further down or up the tree. 24 | * 25 | * @param path The location within database to create a event stream for. 26 | * @return The {@link FirebaseRestEventStream} for the given path. 27 | */ 28 | FirebaseRestEventStream getEventStream(String path); 29 | 30 | /** 31 | * Returns a {@link FirebaseSecurityRulesReference} object for this namespaces. The object can be used 32 | * to retrieve or modify the access rules, add validation or indexes. 33 | * 34 | * @return The {@link FirebaseSecurityRulesReference} for this namespace. 35 | */ 36 | FirebaseSecurityRulesReference getSecurityRules(); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/FirebaseRestDatabaseFactory.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | /** 4 | * A factory to create {@link FirebaseRestDatabase} instances. The implementation will 5 | * take care of the configuration of the {@link com.ning.http.client.AsyncHttpClient} and 6 | * {@link com.google.gson.Gson}. 7 | */ 8 | public interface FirebaseRestDatabaseFactory { 9 | 10 | /** 11 | * Factory method to create a new instance of a {@link FirebaseRestDatabase} for the given URL, using the 12 | * provided access token as the auth query parameter. 13 | * 14 | * @param databaseUrl The URL root URL to the Firebase database, i.e. https://myinstance.firebaseio.com 15 | * @param accessToken The access token to be used for all requests to this database. Value can be null 16 | * @return A new instance of {@link FirebaseRestDatabase} representing the database of the given URL 17 | */ 18 | FirebaseRestDatabase create(String databaseUrl, String accessToken); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/FirebaseRestDatabaseImpl.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | import com.google.gson.Gson; 4 | import com.ning.http.client.AsyncHttpClient; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | /** 9 | * {@link FirebaseRestDatabase} implementation. 10 | */ 11 | class FirebaseRestDatabaseImpl implements FirebaseRestDatabase { 12 | 13 | private static final Logger LOG = LoggerFactory.getLogger(FirebaseRestDatabaseImpl.class); 14 | 15 | private final AsyncHttpClient asyncHttpClient; 16 | private final Gson gson; 17 | private final String namespaceUrl; 18 | private final String firebaseAccessToken; 19 | 20 | FirebaseRestDatabaseImpl( 21 | AsyncHttpClient asyncHttpClient, 22 | Gson gson, 23 | String namespaceUrl, 24 | String firebaseAccessToken 25 | ) { 26 | this.asyncHttpClient = asyncHttpClient; 27 | this.gson = gson; 28 | this.namespaceUrl = PathUtil.normalizePath(namespaceUrl); 29 | this.firebaseAccessToken = firebaseAccessToken; 30 | } 31 | 32 | @Override 33 | public FirebaseRestReference getReference(String path) { 34 | LOG.info("Creating new FirebaseRestReference for path '{}'", path); 35 | 36 | return new FirebaseRestReferenceImpl( 37 | asyncHttpClient, 38 | gson, 39 | namespaceUrl, 40 | firebaseAccessToken, 41 | path 42 | ); 43 | } 44 | 45 | @Override 46 | public FirebaseRestEventStream getEventStream(String path) { 47 | LOG.info("Creating new FirebaseEventStream for path '{}'", path); 48 | 49 | return new FirebaseRestEventStreamImpl( 50 | asyncHttpClient, 51 | gson, 52 | namespaceUrl, 53 | firebaseAccessToken, 54 | path 55 | ); 56 | } 57 | 58 | @Override 59 | public FirebaseSecurityRulesReference getSecurityRules() { 60 | LOG.info("Creating new FirebaseSecurityRulesReference"); 61 | 62 | // FirebaseSecurityRulesReferenceImpl has its own Gson instance, in order to apply special configuration settings 63 | return new FirebaseSecurityRulesReferenceImpl( 64 | asyncHttpClient, 65 | namespaceUrl, 66 | firebaseAccessToken 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/FirebaseRestEventStream.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | import org.jdeferred.ProgressCallback; 4 | import org.jdeferred.Promise; 5 | import org.restonfire.exceptions.FirebaseRuntimeException; 6 | import org.restonfire.responses.StreamingEvent; 7 | 8 | /** 9 | * A {@link FirebaseRestEventStream} represents a specific location within a Firebase database and allows 10 | * for listening to change events like data being set or updated.
11 | *
12 | * This interface is a mixture of Firebase's Java and Javascript API, using similar syntax as the Java (Android) 13 | * API but returning a {@link Promise} to allow for a more functional implementation of asynchronous requests.
14 | * 15 | * @see JDeferred Library 16 | * @see #startListening() for more detailed information about this streaming events. 17 | */ 18 | public interface FirebaseRestEventStream { 19 | 20 | /** 21 | * Returns the fully qualified URL for this FirebaseRestEventStream instance. 22 | * 23 | * @return The absolute URL of the current reference as a String. 24 | */ 25 | String getReferenceUrl(); 26 | 27 | /** 28 | * Starts listening for the events on the current document location of this database. The first event will occur 29 | * immediately and contains the current value at the location. Any consecutive events depend on actual modifications 30 | * applied to the current location or its children.
31 | *
32 | * Individual events will be forwarded to the {@link Promise#progress(ProgressCallback)} function passing in an {@link StreamingEvent} 33 | * object that contains the type, a relative path and the value of the changed location in Firebase. 34 | *
35 | * The promise will be resolved once the stopListening() function is invoked.
36 | *
37 | * If the stream was forced to close by a change of security rules or expiration of the access token, the promise will 38 | * be rejected. 39 | * 40 | * @throws org.restonfire.exceptions.FirebaseInvalidStateException The listener for the events has already been started. 41 | * @throws org.restonfire.exceptions.FirebaseAccessException The client does not have permission to read from this location. 42 | * @throws org.restonfire.exceptions.FirebaseAuthenticationExpiredException The session for the given access token has expired. 43 | * 44 | * @return A {@link Promise} for the active request. 45 | */ 46 | Promise startListening(); 47 | 48 | /** 49 | * Closes the event stream, which will resolve the promise created for this location. 50 | * 51 | * @throws org.restonfire.exceptions.FirebaseInvalidStateException There is no event listener currently running. 52 | */ 53 | void stopListening(); 54 | 55 | /** 56 | * Returns the streaming reference for the root of this Firebase database. 57 | * 58 | * @return The {@link FirebaseRestEventStream} representing the root of this Firebase database. 59 | */ 60 | FirebaseRestEventStream getRoot(); 61 | 62 | /** 63 | * Returns the streaming reference for the parent location of this {@link FirebaseRestEventStream}. 64 | * 65 | * @return The {@link FirebaseRestEventStream} representing the parent location of this {@link FirebaseRestEventStream}. 66 | */ 67 | FirebaseRestEventStream getParent(); 68 | 69 | /** 70 | * Returns the streaming reference for the given child location of this {@link FirebaseRestEventStream}. 71 | * 72 | * @param path The child's name or path to the child relative to this reference. 73 | * @return The {@link FirebaseRestEventStream} representing the child location. 74 | */ 75 | FirebaseRestEventStream child(String path); 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/FirebaseRestEventStreamImpl.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | import com.google.gson.Gson; 4 | import com.ning.http.client.*; 5 | import org.jdeferred.Deferred; 6 | import org.jdeferred.Promise; 7 | import org.jdeferred.impl.DeferredObject; 8 | import org.restonfire.exceptions.*; 9 | import org.restonfire.responses.StreamingEvent; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.io.BufferedReader; 14 | import java.io.ByteArrayInputStream; 15 | import java.io.IOException; 16 | import java.io.InputStreamReader; 17 | import java.net.HttpURLConnection; 18 | import java.nio.charset.Charset; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | 22 | /** 23 | * Non-thread safe {@link FirebaseRestEventStream} implementation. 24 | */ 25 | @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.StdCyclomaticComplexity", "checkstyle:classdataabstractioncoupling", "checkstyle:classfanoutcomplexity"}) 26 | class FirebaseRestEventStreamImpl extends FirebaseDocumentLocation implements FirebaseRestEventStream { 27 | 28 | private static final Logger LOG = LoggerFactory.getLogger(FirebaseRestEventStreamImpl.class); 29 | 30 | private static final Map EVENT_TYPE_MAPPER; 31 | static { 32 | EVENT_TYPE_MAPPER = new HashMap<>(5); 33 | EVENT_TYPE_MAPPER.put("put", StreamingEvent.EventType.Set); 34 | EVENT_TYPE_MAPPER.put("patch", StreamingEvent.EventType.Update); 35 | EVENT_TYPE_MAPPER.put("keep-alive", StreamingEvent.EventType.KeepAlive); 36 | EVENT_TYPE_MAPPER.put("cancel", StreamingEvent.EventType.Cancel); 37 | EVENT_TYPE_MAPPER.put("auth_revoked", StreamingEvent.EventType.Expired); 38 | } 39 | 40 | private final Gson gson; 41 | private final AsyncHttpClient asyncHttpClient; 42 | private final AsyncHttpClient.BoundRequestBuilder eventStreamRequest; 43 | 44 | private ListenableFuture currentListener; 45 | 46 | FirebaseRestEventStreamImpl( 47 | AsyncHttpClient asyncHttpClient, 48 | Gson gson, 49 | String fbBaseUrl, 50 | String fbAccessToken, 51 | String path) { 52 | 53 | super(fbBaseUrl, path, fbAccessToken); 54 | 55 | this.asyncHttpClient = asyncHttpClient; 56 | this.gson = gson; 57 | 58 | this.eventStreamRequest = RequestBuilderUtil.createGet( 59 | asyncHttpClient, 60 | referenceUrl, 61 | fbAccessToken 62 | ).addHeader("Accept", "text/event-stream") 63 | .setFollowRedirects(true); 64 | } 65 | 66 | @Override 67 | public Promise startListening() { 68 | LOG.debug("startListening() invoked for reference {}", referenceUrl); 69 | 70 | if (currentListener != null) { 71 | throw new FirebaseInvalidStateException(FirebaseRuntimeException.ErrorCode.EventStreamListenerAlreadyActive, "The EventStream is already running"); 72 | } 73 | 74 | final Deferred deferred = new DeferredObject<>(); 75 | final AsyncHandler asyncRequestHandler = createAsyncHandler(deferred); 76 | currentListener = eventStreamRequest.execute(asyncRequestHandler); 77 | 78 | return deferred.promise(); 79 | } 80 | 81 | @Override 82 | public void stopListening() { 83 | if (currentListener == null) { 84 | throw new FirebaseInvalidStateException(FirebaseRuntimeException.ErrorCode.EventStreamListenerNotActive, "The EventStream is currently not active"); 85 | } 86 | 87 | currentListener.done(); 88 | currentListener = null; 89 | } 90 | 91 | @Override 92 | public FirebaseRestEventStream getRoot() { 93 | LOG.debug("getRoot() invoked for reference {}", referenceUrl); 94 | return new FirebaseRestEventStreamImpl( 95 | asyncHttpClient, 96 | gson, 97 | fbBaseUrl, 98 | fbAccessToken, 99 | "" 100 | ); 101 | } 102 | 103 | @Override 104 | public FirebaseRestEventStream getParent() { 105 | LOG.debug("getParent() invoked for reference {}", referenceUrl); 106 | return new FirebaseRestEventStreamImpl( 107 | asyncHttpClient, 108 | gson, 109 | fbBaseUrl, 110 | fbAccessToken, 111 | PathUtil.getParent(path) 112 | ); 113 | } 114 | 115 | @Override 116 | public FirebaseRestEventStream child(String childPath) { 117 | LOG.debug("child({}) invoked for reference {}", childPath, referenceUrl); 118 | return new FirebaseRestEventStreamImpl( 119 | asyncHttpClient, 120 | gson, 121 | fbBaseUrl, 122 | fbAccessToken, 123 | PathUtil.concatenatePath(path, childPath) 124 | ); 125 | } 126 | 127 | @SuppressWarnings({"PMD.ExcessiveMethodLength", "checkstyle:anoninnerlength"}) 128 | private AsyncHandler createAsyncHandler(final Deferred deferred) { 129 | return new AsyncHandler() { 130 | @Override 131 | public void onThrowable(Throwable t) { 132 | final String message = "EventStream request for location '" + referenceUrl + "' failed"; 133 | LOG.error(message, t); 134 | deferred.reject(new FirebaseRestException(FirebaseRuntimeException.ErrorCode.EventStreamRequestFailed, message, t)); 135 | } 136 | 137 | @Override 138 | public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { 139 | LOG.debug("Received event"); 140 | 141 | final StreamingEvent response = parseResponse(bodyPart.getBodyPartBytes()); 142 | 143 | switch (response.getEventType()) { 144 | case KeepAlive: 145 | break; 146 | case Cancel: 147 | deferred.reject(new FirebaseAccessException(referenceUrl)); 148 | break; 149 | case Expired: 150 | deferred.reject(new FirebaseAuthenticationExpiredException(referenceUrl)); 151 | break; 152 | default: 153 | deferred.notify(response); 154 | break; 155 | } 156 | 157 | return STATE.CONTINUE; 158 | } 159 | 160 | @Override 161 | public STATE onStatusReceived(HttpResponseStatus responseStatus) throws Exception { 162 | LOG.info("Received Status: " + responseStatus.getStatusCode()); 163 | switch (responseStatus.getStatusCode()) { 164 | // 307 = Temporary Redirect 165 | case 307: 166 | case HttpURLConnection.HTTP_OK: 167 | break; 168 | case HttpURLConnection.HTTP_UNAUTHORIZED: 169 | case HttpURLConnection.HTTP_FORBIDDEN: 170 | LOG.warn("The request to '{}' that violates the Security and Firebase Rules", referenceUrl); 171 | deferred.reject(new FirebaseAccessException(responseStatus)); 172 | break; 173 | default: 174 | LOG.error("Unsupported status code: " + responseStatus.getStatusCode()); 175 | deferred.reject(new FirebaseRestException(FirebaseRuntimeException.ErrorCode.UnsupportedStatusCode, responseStatus)); 176 | break; 177 | } 178 | 179 | return STATE.CONTINUE; 180 | } 181 | 182 | @Override 183 | public STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception { 184 | LOG.debug("Received headers"); 185 | return STATE.CONTINUE; 186 | } 187 | 188 | @Override 189 | public Void onCompleted() throws Exception { 190 | LOG.info("DONE"); 191 | deferred.resolve(null); 192 | return null; 193 | } 194 | }; 195 | } 196 | 197 | private StreamingEvent parseResponse(byte[] response) throws IOException { 198 | 199 | try (ByteArrayInputStream is = new ByteArrayInputStream(response); 200 | BufferedReader reader = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")))) { 201 | 202 | final StreamingEvent.EventType eventType = getEventType(reader.readLine()); 203 | final String eventData = getEventData(reader.readLine()); 204 | 205 | return new StreamingEvent(gson, eventType, eventData); 206 | } 207 | } 208 | 209 | private StreamingEvent.EventType getEventType(String eventString) { 210 | LOG.debug("Mapping event type -> " + eventString); 211 | 212 | // eventString format -> event: 213 | return EVENT_TYPE_MAPPER.get( 214 | eventString.replaceFirst("event:", "").trim().toLowerCase() 215 | ); 216 | } 217 | 218 | private String getEventData(String dataString) { 219 | LOG.debug("Extracting event data -> " + dataString); 220 | 221 | // dataString format -> data: 222 | return dataString.replaceFirst("data:", "").trim(); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/FirebaseRestQuery.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | import org.jdeferred.Promise; 4 | import org.restonfire.exceptions.FirebaseRuntimeException; 5 | 6 | /** 7 | * A {@link FirebaseRestQuery} is create by a {@link FirebaseRestReference} and represents the same location as the 8 | * creating REST reference. It allows for querying the location with the various different sort and filter 9 | * mechanisms that Firebase supports.
10 | *
11 | * For a full understanding of the behaviour of this class, please visit 12 | * Firebase's documentation for more 13 | * details.
14 | * Firebase's old documentation is also a 15 | * great resource to understand the behaviour of the supported methods. 16 | * 17 | * @see Firebase Filtering Data Documentation 18 | */ 19 | public interface FirebaseRestQuery { 20 | 21 | /** 22 | * Return items greater than or equal to the specified key, value, or priority, depending on the order-by method chosen. 23 | * All values less (or before) the given value will be removed from the result. 24 | * 25 | * @param value The starting value of the result set. 26 | * @return This {@link FirebaseRestQuery} reference. 27 | * @see Firebase Complex Filtering Documentation 28 | */ 29 | FirebaseRestQuery startAt(Object value); 30 | 31 | /** 32 | * Return items less than or equal to the specified key, value, or priority, depending on the order-by method chosen. 33 | * All values greater (or after) the given value will be removed from the result. 34 | * 35 | * @param value The ending value of the result set. 36 | * @return This {@link FirebaseRestQuery} reference. 37 | * @see Firebase Complex Filtering Documentation 38 | */ 39 | FirebaseRestQuery endAt(Object value); 40 | 41 | /** 42 | * Return items equal to the specified key, value, or priority, depending on the order-by method chosen. 43 | * All other values will be removed from the result. 44 | * 45 | * @param value The value all results must be equal to. 46 | * @return This {@link FirebaseRestQuery} reference. 47 | * @see Firebase Complex Filtering Documentation 48 | */ 49 | FirebaseRestQuery equalTo(Object value); 50 | 51 | /** 52 | * Sets the maximum number of items to return from the beginning of the ordered list of results. 53 | * All other values will be removed from the result. 54 | * 55 | * @param number Number of results to be returned. 56 | * @return This {@link FirebaseRestQuery} reference. 57 | * @see Firebase Complex Filtering Documentation 58 | */ 59 | FirebaseRestQuery limitToFirst(int number); 60 | 61 | /** 62 | * Sets the maximum number of items to return from the end of the ordered list of results. 63 | * All other values will be removed from the result. 64 | * 65 | * @param number Number of results to be returned. 66 | * @return This {@link FirebaseRestQuery} reference. 67 | * @see Firebase Complex Filtering Documentation 68 | */ 69 | FirebaseRestQuery limitToLast(int number); 70 | 71 | /** 72 | * Order results by child keys. This is particularly useful for 73 | * non-generated keys. 74 | * 75 | * @return This {@link FirebaseRestQuery} reference. 76 | * @see Firebase Order By Key Documentation 77 | */ 78 | FirebaseRestQuery orderByKey(); 79 | 80 | /** 81 | * Order results by the value of a specified child name. 82 | * 83 | * @return This {@link FirebaseRestQuery} reference. 84 | * @see Firebase Order By Documentation 85 | */ 86 | FirebaseRestQuery orderByChild(String name); 87 | 88 | /** 89 | * Order results by the assigned priority. 90 | * 91 | * @return This {@link FirebaseRestQuery} reference. 92 | * @see Firebase Order By Priority Documentation 93 | */ 94 | FirebaseRestQuery orderByPriority(); 95 | 96 | /** 97 | * Order results by child values. 98 | * 99 | * @return This {@link FirebaseRestQuery} reference. 100 | * @see Firebase Order By Value Documentation 101 | */ 102 | FirebaseRestQuery orderByValue(); 103 | 104 | /** 105 | * Removes all previously provided sorting and filtering values. This resets the query and alternative values 106 | * can now be used. 107 | */ 108 | void clear(); 109 | 110 | /** 111 | * Executes the query with the previously provided sorting and filtering arguments. The function can be invoked 112 | * multiple times to re-run the same query over and over again.
113 | *
114 | * The promise returned will be rejected with the following two exceptions:
115 | *
    116 | *
  • org.restonfire.exceptions.FirebaseAccessException - A {@link FirebaseRuntimeException} in the case that 117 | * access to the data for this reference was denied. 118 | *
  • 119 | *
  • org.restonfire.exceptions.FirebaseRestException - A {@link FirebaseRuntimeException} in the case that an 120 | * unexpected status code was returned or the deserialization of the response into the type parameter fails. 121 | *
  • 122 | *
123 | * @param clazz The {@link Class} type for the POJO to be created for the data returned by the request. 124 | * @param The type of the result object. 125 | * @return A promise which will be resolved with the POJO generated from the response if the request was successful. 126 | */ 127 | Promise run(Class clazz); 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/FirebaseRestQueryImpl.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | import com.google.gson.Gson; 4 | import com.ning.http.client.AsyncCompletionHandler; 5 | import com.ning.http.client.AsyncHttpClient; 6 | import com.ning.http.client.Param; 7 | import com.ning.http.client.Response; 8 | import org.jdeferred.Deferred; 9 | import org.jdeferred.Promise; 10 | import org.jdeferred.impl.DeferredObject; 11 | import org.restonfire.exceptions.FirebaseInvalidStateException; 12 | import org.restonfire.exceptions.FirebaseRuntimeException; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.util.ArrayList; 17 | import java.util.HashMap; 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | /** 22 | * Created by jfischer on 2016-05-28. 23 | */ 24 | class FirebaseRestQueryImpl implements FirebaseRestQuery { 25 | 26 | private static final Logger LOG = LoggerFactory.getLogger(FirebaseRestQueryImpl.class); 27 | 28 | private static final String ORDER_BY = "orderBy"; 29 | private static final String START_AT = "startAt"; 30 | private static final String END_AT = "endAt"; 31 | private static final String EQUAL_TO = "equalTo"; 32 | private static final String LIMIT_FIRST = "limitToFirst"; 33 | private static final String LIMIT_LAST = "limitToLast"; 34 | 35 | private final Gson gson; 36 | private final String referenceUrl; 37 | private final AsyncHttpClient.BoundRequestBuilder queryRequest; 38 | 39 | private final Map queryParams = new HashMap<>(); 40 | 41 | FirebaseRestQueryImpl( 42 | Gson gson, 43 | AsyncHttpClient.BoundRequestBuilder requestBuilder, 44 | String referenceUrl 45 | ) { 46 | this.gson = gson; 47 | this.queryRequest = requestBuilder; 48 | this.referenceUrl = referenceUrl; 49 | } 50 | 51 | @Override 52 | public FirebaseRestQuery startAt(Object val) { 53 | return setParameter(START_AT, val); 54 | } 55 | 56 | @Override 57 | public FirebaseRestQuery endAt(Object val) { 58 | return setParameter(END_AT, val); 59 | } 60 | 61 | @Override 62 | public FirebaseRestQuery equalTo(Object val) { 63 | return setParameter(EQUAL_TO, val); 64 | } 65 | 66 | @Override 67 | public FirebaseRestQuery limitToFirst(int number) { 68 | return setParameter(LIMIT_FIRST, number); 69 | } 70 | 71 | @Override 72 | public FirebaseRestQuery limitToLast(int number) { 73 | return setParameter(LIMIT_LAST, number); 74 | } 75 | 76 | @Override 77 | public FirebaseRestQuery orderByKey() { 78 | return setParameter(ORDER_BY, "$key"); 79 | } 80 | 81 | @Override 82 | public FirebaseRestQuery orderByChild(String name) { 83 | return setParameter(ORDER_BY, name); 84 | } 85 | 86 | @Override 87 | public FirebaseRestQuery orderByPriority() { 88 | return setParameter(ORDER_BY, "$priority"); 89 | } 90 | 91 | @Override 92 | public FirebaseRestQuery orderByValue() { 93 | return setParameter(ORDER_BY, "$value"); 94 | } 95 | 96 | @Override 97 | public void clear() { 98 | queryParams.clear(); 99 | } 100 | 101 | @Override 102 | public Promise run(final Class clazz) { 103 | LOG.debug("Running query({}) invoked for reference {}. Filters: {}", clazz, referenceUrl, gson.toJson(queryParams)); 104 | 105 | final List params = new ArrayList<>(queryParams.size()); 106 | 107 | for (Map.Entry entry : queryParams.entrySet()) { 108 | params.add(new Param(entry.getKey(), entry.getValue())); 109 | } 110 | 111 | if (!params.isEmpty()) { 112 | queryRequest.addQueryParams(params); 113 | } 114 | 115 | final Deferred deferred = new DeferredObject<>(); 116 | 117 | queryRequest.execute(new AsyncCompletionHandler() { 118 | 119 | @Override 120 | public Void onCompleted(Response response) throws Exception { 121 | try { 122 | LOG.debug("Request for getValue({}) completed", clazz); 123 | final T result = handleResponse(response, clazz); 124 | deferred.resolve(result); 125 | } catch (FirebaseRuntimeException ex) { 126 | deferred.reject(ex); 127 | } 128 | return null; 129 | } 130 | }); 131 | 132 | return deferred.promise(); 133 | } 134 | 135 | private FirebaseRestQuery setParameter(String key, Object value) { 136 | if (queryParams.containsKey(key)) { 137 | throw new FirebaseInvalidStateException(FirebaseRuntimeException.ErrorCode.QueryParamAlreadySet, key + " parameter has already been set"); 138 | } 139 | 140 | queryParams.put(key, gson.toJson(value)); 141 | 142 | return this; 143 | } 144 | 145 | private T handleResponse(Response response, Class clazz) { 146 | return RestUtil.handleResponse(gson, referenceUrl, response, clazz); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/FirebaseSecurityRulesReference.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | import org.jdeferred.Promise; 4 | import org.restonfire.exceptions.FirebaseRuntimeException; 5 | import org.restonfire.responses.FirebaseSecurityRules; 6 | 7 | /** 8 | * {@link FirebaseSecurityRulesReference} allows for updating of Firebase's security rules. It can be used 9 | * to get or set the security rules using the REST API.
10 | *
11 | * Note: The security rules requires the access token to be a Firebase secret! 12 | */ 13 | public interface FirebaseSecurityRulesReference { 14 | 15 | /** 16 | * Retrieves the rules currently configured for the {@link FirebaseRestDatabase}. 17 | * 18 | * @return The FirebaseSecurityRules 19 | */ 20 | Promise get(); 21 | 22 | /** 23 | * Replaces the existing security rules with the ones provided when invoking this method. 24 | * All previous rules will be replaced in this method. 25 | * 26 | * @param newRules The new set of rules that should be applied from now on. 27 | * 28 | * @return A promise which will resolve with the {@link FirebaseSecurityRules} object passed in as the value if the request was successful. 29 | */ 30 | Promise set(FirebaseSecurityRules newRules); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/FirebaseSecurityRulesReferenceImpl.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.ning.http.client.AsyncCompletionHandler; 6 | import com.ning.http.client.AsyncHttpClient; 7 | import com.ning.http.client.Response; 8 | import org.jdeferred.Deferred; 9 | import org.jdeferred.Promise; 10 | import org.jdeferred.impl.DeferredObject; 11 | import org.restonfire.exceptions.FirebaseRuntimeException; 12 | import org.restonfire.responses.FirebaseSecurityRules; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | /** 17 | * {@link FirebaseSecurityRulesReference} implementation. 18 | */ 19 | class FirebaseSecurityRulesReferenceImpl implements FirebaseSecurityRulesReference { 20 | 21 | private static final Logger LOG = LoggerFactory.getLogger(FirebaseSecurityRulesReferenceImpl.class); 22 | 23 | private final Gson gson = new GsonBuilder() 24 | .setPrettyPrinting() 25 | .disableHtmlEscaping() 26 | .create(); 27 | 28 | private final AsyncHttpClient asyncHttpClient; 29 | 30 | private final String fbAccessToken; 31 | private final String referenceUrl; 32 | 33 | FirebaseSecurityRulesReferenceImpl( 34 | AsyncHttpClient asyncHttpClient, 35 | String fbBaseUrl, 36 | String fbAccessToken 37 | ) { 38 | this.asyncHttpClient = asyncHttpClient; 39 | this.fbAccessToken = fbAccessToken; 40 | this.referenceUrl = PathUtil.concatenatePath(fbBaseUrl, ".settings/rules") + FirebaseDocumentLocation.JSON_SUFFIX; 41 | } 42 | 43 | @Override 44 | public Promise get() { 45 | LOG.debug("getValue() invoked for security rules"); 46 | final Deferred deferred = new DeferredObject<>(); 47 | 48 | final AsyncHttpClient.BoundRequestBuilder getRequest = RequestBuilderUtil.createGet(asyncHttpClient, referenceUrl, fbAccessToken); 49 | 50 | getRequest.execute(new AsyncCompletionHandler() { 51 | 52 | @Override 53 | public Void onCompleted(Response response) throws Exception { 54 | try { 55 | LOG.debug("Request for getValue() completed"); 56 | final FirebaseSecurityRules result = handleResponse(response); 57 | deferred.resolve(result); 58 | } catch (FirebaseRuntimeException ex) { 59 | deferred.reject(ex); 60 | } 61 | return null; 62 | } 63 | }); 64 | 65 | return deferred.promise(); 66 | } 67 | 68 | @Override 69 | public Promise set(final FirebaseSecurityRules newRules) { 70 | LOG.debug("setValue() invoked for security rules"); 71 | final Deferred deferred = new DeferredObject<>(); 72 | 73 | final AsyncHttpClient.BoundRequestBuilder putRequest = RequestBuilderUtil.createPut(asyncHttpClient, referenceUrl, fbAccessToken, gson.toJson(newRules)); 74 | 75 | putRequest.execute(new AsyncCompletionHandler() { 76 | 77 | @Override 78 | public Void onCompleted(Response response) throws Exception { 79 | LOG.debug("Request for setValue() completed"); 80 | return handleValueModifiedResponse(response, deferred, newRules); 81 | } 82 | }); 83 | 84 | return deferred.promise(); 85 | } 86 | 87 | private Void handleValueModifiedResponse(Response response, Deferred deferred, FirebaseSecurityRules value) { 88 | try { 89 | handleResponse(response); 90 | deferred.resolve(value); 91 | } catch (FirebaseRuntimeException ex) { 92 | deferred.reject(ex); 93 | } 94 | return null; 95 | } 96 | 97 | private FirebaseSecurityRules handleResponse(Response response) { 98 | return RestUtil.handleResponse(gson, referenceUrl, response, FirebaseSecurityRules.class); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/PathUtil.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | /** 4 | * Utility class for path operations. 5 | */ 6 | final class PathUtil { 7 | 8 | public static final String FORWARD_SLASH = "/"; 9 | 10 | private static final String PATH_CANNOT_BE_NULL = "path cannot be null"; 11 | 12 | private PathUtil() { 13 | // do nothing 14 | } 15 | 16 | public static String getParent(String path) { 17 | if (path == null) { 18 | throw new IllegalArgumentException(PATH_CANNOT_BE_NULL); 19 | } 20 | 21 | final String normalizedPath = normalizePath(path); 22 | 23 | if (normalizedPath.length() == 0) { 24 | // root location 25 | return null; 26 | } 27 | 28 | final int index = normalizedPath.lastIndexOf('/'); 29 | return index > 0 30 | ? path.substring(0, index) 31 | : ""; 32 | } 33 | 34 | public static String concatenatePath(String path, String child) { 35 | if (path == null) { 36 | throw new IllegalArgumentException(PATH_CANNOT_BE_NULL); 37 | } 38 | 39 | final String normalizedPath = normalizePath(path); 40 | final String normalizedChild = normalizePath(child); 41 | 42 | return normalizedPath.length() > 0 43 | ? normalizedPath + FORWARD_SLASH + normalizedChild 44 | : normalizedChild; 45 | } 46 | 47 | public static String normalizePath(String path) { 48 | return path.endsWith(FORWARD_SLASH) 49 | ? path.substring(0, path.length() - 1) 50 | : path; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/RequestBuilderUtil.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | import com.ning.http.client.AsyncHttpClient; 4 | 5 | /** 6 | * Utility class to build the requests to Firebase. 7 | */ 8 | final class RequestBuilderUtil { 9 | private RequestBuilderUtil() { 10 | //do nothing 11 | } 12 | 13 | public static AsyncHttpClient.BoundRequestBuilder createGet(AsyncHttpClient asyncHttpClient, String referenceUrl, String accessToken) { 14 | final AsyncHttpClient.BoundRequestBuilder requestBuilder = asyncHttpClient 15 | .prepareGet(referenceUrl); 16 | 17 | return addQueryParamsIfApplicable(requestBuilder, accessToken); 18 | } 19 | 20 | public static AsyncHttpClient.BoundRequestBuilder createPost(AsyncHttpClient asyncHttpClient, String referenceUrl, String accessToken, String body) { 21 | final AsyncHttpClient.BoundRequestBuilder requestBuilder = asyncHttpClient 22 | .preparePost(referenceUrl) 23 | .setBody(body); 24 | 25 | return addQueryParamsIfApplicable(requestBuilder, accessToken); 26 | } 27 | 28 | public static AsyncHttpClient.BoundRequestBuilder createPatch(AsyncHttpClient asyncHttpClient, String referenceUrl, String accessToken, String body) { 29 | final AsyncHttpClient.BoundRequestBuilder requestBuilder = asyncHttpClient 30 | .preparePatch(referenceUrl) 31 | .setBody(body); 32 | 33 | return addQueryParamsIfApplicable(requestBuilder, accessToken); 34 | } 35 | 36 | public static AsyncHttpClient.BoundRequestBuilder createPut(AsyncHttpClient asyncHttpClient, String referenceUrl, String accessToken, String body) { 37 | final AsyncHttpClient.BoundRequestBuilder requestBuilder = asyncHttpClient 38 | .preparePut(referenceUrl) 39 | .setBody(body); 40 | 41 | return addQueryParamsIfApplicable(requestBuilder, accessToken); 42 | } 43 | 44 | public static AsyncHttpClient.BoundRequestBuilder createDelete(AsyncHttpClient asyncHttpClient, String referenceUrl, String accessToken) { 45 | final AsyncHttpClient.BoundRequestBuilder requestBuilder = asyncHttpClient 46 | .prepareDelete(referenceUrl); 47 | 48 | return addQueryParamsIfApplicable(requestBuilder, accessToken); 49 | } 50 | 51 | private static AsyncHttpClient.BoundRequestBuilder addQueryParamsIfApplicable(AsyncHttpClient.BoundRequestBuilder requestBuilder, String accessToken) { 52 | if (StringUtil.notNullOrEmpty(accessToken)) { 53 | return requestBuilder.addQueryParam("auth", accessToken); 54 | } 55 | 56 | return requestBuilder; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/RestUtil.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonSyntaxException; 5 | import com.ning.http.client.Response; 6 | import org.restonfire.exceptions.FirebaseAccessException; 7 | import org.restonfire.exceptions.FirebaseRestException; 8 | import org.restonfire.exceptions.FirebaseRuntimeException; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.io.IOException; 13 | import java.net.HttpURLConnection; 14 | 15 | /** 16 | * Helper class for common REST based functions. 17 | */ 18 | final class RestUtil { 19 | 20 | private static final Logger LOG = LoggerFactory.getLogger(RestUtil.class); 21 | 22 | private static final String FAILED_TO_PARSE_RESPONSE_BODY_FOR_REQUEST = "Failed to parse responses body for request: "; 23 | 24 | private RestUtil() { 25 | //do nothing 26 | } 27 | 28 | public static T handleResponse(Gson gson, String referenceUrl, Response response, Class clazz) { 29 | try { 30 | switch (response.getStatusCode()) { 31 | case HttpURLConnection.HTTP_OK: 32 | return clazz == null 33 | ? null 34 | : gson.fromJson(response.getResponseBody(), clazz); 35 | case HttpURLConnection.HTTP_UNAUTHORIZED: 36 | case HttpURLConnection.HTTP_FORBIDDEN: 37 | LOG.warn("The request to '{}' that violates the Security and Firebase Rules", referenceUrl); 38 | throw new FirebaseAccessException(response); 39 | default: 40 | LOG.error("Unsupported status code ({}), body: {}", response.getStatusCode(), response.getResponseBody()); 41 | throw new FirebaseRestException(FirebaseRuntimeException.ErrorCode.UnsupportedStatusCode, response); 42 | } 43 | } catch (JsonSyntaxException | IOException e) { 44 | LOG.error(FAILED_TO_PARSE_RESPONSE_BODY_FOR_REQUEST + response.getUri(), e); 45 | throw new FirebaseRestException(FirebaseRuntimeException.ErrorCode.ResponseDeserializationFailure, FAILED_TO_PARSE_RESPONSE_BODY_FOR_REQUEST + response.getUri(), e); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/ServerValues.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | import java.util.Collections; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | /** 8 | * This class contains all Firebase server values such as a timestamp. 9 | */ 10 | public final class ServerValues { 11 | 12 | /** 13 | * A server timestamp value. 14 | * 15 | * @see Firebase REST Server Values Documentation 16 | */ 17 | public static final Map TIMESTAMP; 18 | 19 | static { 20 | final HashMap timestamp = new HashMap<>(1); 21 | timestamp.put(".sv", "timestamp"); 22 | TIMESTAMP = Collections.unmodifiableMap(timestamp); 23 | } 24 | 25 | private ServerValues() { 26 | // do not use 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/StringUtil.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | /** 4 | * Utility class for String operations. 5 | */ 6 | final class StringUtil { 7 | 8 | private StringUtil() { 9 | // do nothing 10 | } 11 | 12 | public static boolean notNullOrEmpty(String aString) { 13 | return aString != null && aString.length() > 0; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/exceptions/FirebaseAccessException.java: -------------------------------------------------------------------------------- 1 | package org.restonfire.exceptions; 2 | 3 | import com.ning.http.client.HttpResponseStatus; 4 | import com.ning.http.client.Response; 5 | 6 | import java.io.IOException; 7 | 8 | /** 9 | * Exception thrown if the operation on a Firebase reference was not permitted 10 | * under the current security rules. 11 | */ 12 | public final class FirebaseAccessException extends FirebaseRuntimeException { 13 | 14 | public static final String ERROR_MESSAGE = "The access to the reference '%s' was not permitted. Status code: %s"; 15 | 16 | public FirebaseAccessException(Response response) throws IOException { 17 | super(ErrorCode.AccessViolation, String.format(ERROR_MESSAGE, response.getUri(), response.getStatusCode())); 18 | } 19 | 20 | public FirebaseAccessException(HttpResponseStatus responseStatus) { 21 | super(ErrorCode.AccessViolation, String.format(ERROR_MESSAGE, responseStatus.getUri(), responseStatus.getStatusCode())); 22 | } 23 | 24 | public FirebaseAccessException(String referenceUrl) { 25 | super(ErrorCode.AccessViolation, String.format("The access to the reference '%s' has been revoked.", referenceUrl)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/exceptions/FirebaseAuthenticationExpiredException.java: -------------------------------------------------------------------------------- 1 | package org.restonfire.exceptions; 2 | 3 | /** 4 | * Created by jfischer on 2016-05-06. 5 | */ 6 | public class FirebaseAuthenticationExpiredException extends FirebaseRuntimeException { 7 | 8 | public FirebaseAuthenticationExpiredException(String referenceUrl) { 9 | super(ErrorCode.AuthenticationExpired, "The access token for this connection has expired. Location: " + referenceUrl); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/exceptions/FirebaseInvalidStateException.java: -------------------------------------------------------------------------------- 1 | package org.restonfire.exceptions; 2 | 3 | /** 4 | * Created by jfischer on 2016-05-08. 5 | */ 6 | public class FirebaseInvalidStateException extends FirebaseRuntimeException { 7 | 8 | public FirebaseInvalidStateException(ErrorCode errorCode, String message) { 9 | super(errorCode, message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/exceptions/FirebaseRestException.java: -------------------------------------------------------------------------------- 1 | package org.restonfire.exceptions; 2 | 3 | import com.ning.http.client.HttpResponseStatus; 4 | import com.ning.http.client.Response; 5 | 6 | import java.io.IOException; 7 | 8 | /** 9 | * A request to Firebase's REST API failed with an unexpected status code. 10 | */ 11 | public final class FirebaseRestException extends FirebaseRuntimeException { 12 | 13 | private static final String ERROR_MESSAGE = "The REST request to '%s' failed with the following status code: %s"; 14 | 15 | public FirebaseRestException(ErrorCode errorCode, Response response) throws IOException { 16 | super(errorCode, String.format(ERROR_MESSAGE, response.getUri(), response.getStatusCode())); 17 | } 18 | 19 | public FirebaseRestException(ErrorCode errorCode, HttpResponseStatus responseStatus) { 20 | super(errorCode, String.format(ERROR_MESSAGE, responseStatus.getUri(), responseStatus.getStatusCode())); 21 | } 22 | 23 | public FirebaseRestException(ErrorCode errorCode, String message, Throwable cause) { 24 | super(errorCode, message, cause); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/exceptions/FirebaseRuntimeException.java: -------------------------------------------------------------------------------- 1 | package org.restonfire.exceptions; 2 | 3 | /** 4 | * Base exception of all Firebase related exceptions thrown by this API. 5 | * This will allow to set up a single catch block for all exception instead of 6 | * having to handle every exception separately. 7 | * 8 | * This exception is abstract and cannot be instantiated. 9 | * There should always be a more specific exception that 10 | * should be used when throwing an error. 11 | */ 12 | public abstract class FirebaseRuntimeException extends RuntimeException { 13 | 14 | private final ErrorCode errorCode; 15 | 16 | public FirebaseRuntimeException(ErrorCode errorCode, String message) { 17 | super(message); 18 | this.errorCode = errorCode; 19 | } 20 | 21 | public FirebaseRuntimeException(ErrorCode errorCode, String message, Throwable cause) { 22 | super(message, cause); 23 | this.errorCode = errorCode; 24 | } 25 | 26 | public ErrorCode getErrorCode() { 27 | return errorCode; 28 | } 29 | 30 | /** 31 | * Enum describing the different errors that can occur within this library. 32 | */ 33 | public enum ErrorCode { 34 | AccessViolation, 35 | AuthenticationExpired, 36 | ResponseDeserializationFailure, 37 | UnsupportedStatusCode, 38 | EventStreamListenerAlreadyActive, 39 | EventStreamListenerNotActive, 40 | QueryParamAlreadySet, 41 | EventStreamRequestFailed 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/responses/FirebaseSecurityRules.java: -------------------------------------------------------------------------------- 1 | package org.restonfire.responses; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.google.gson.reflect.TypeToken; 6 | 7 | import java.lang.reflect.Type; 8 | import java.util.Map; 9 | 10 | /** 11 | * POJO representing the Firebase rules model consisting of a single property named "rules", 12 | * which contains a map of nested key-value pairs. Keys are always strings, while objects can 13 | * either be boolean, an expression string, or a map of key-value pairs for the node's children.
14 | *
15 | * This class does also contain constants for common key names such as ".read". 16 | * 17 | * @see Firebase Security Rules 18 | */ 19 | public class FirebaseSecurityRules { 20 | 21 | private static final Type RULES_TYPE = new TypeToken>() { }.getType(); 22 | 23 | /** 24 | * The key to be used for setting write access. 25 | */ 26 | public static final String WRITE_KEY = ".write"; 27 | 28 | /** 29 | * The key to be used for setting read access. 30 | */ 31 | public static final String READ_KEY = ".read"; 32 | 33 | /** 34 | * The key to be used for validating new inputs. 35 | */ 36 | public static final String VALIDATE_KEY = ".validate"; 37 | 38 | /** 39 | * The key to be used when setting an index. 40 | */ 41 | public static final String INDEX_KEY = ".indexOn"; 42 | 43 | private final Map rules; 44 | 45 | public FirebaseSecurityRules() { 46 | this(null); 47 | } 48 | 49 | private final transient Gson gson = new GsonBuilder() 50 | .disableHtmlEscaping() 51 | .create(); 52 | 53 | public FirebaseSecurityRules(Map rules) { 54 | this.rules = rules; 55 | } 56 | 57 | /** 58 | * Returns the representation of the Firebase in form of a map. Each key is either the name of the permission 59 | * (.read, .write), the validation rule (.validate), or the name of the property or variables. The value can either 60 | * consist of a {@link Boolean} value, a {@link String} of this key's rule definition, or another map representing 61 | * the rules of the children of the current node. 62 | * 63 | * @return A deep map of the firebase rules. 64 | * 65 | * @see Firebase Database Security Rules for more information 66 | */ 67 | public Map getRules() { 68 | //TODO: Review if using gson is the best method for the deep copy 69 | return gson.fromJson(gson.toJson(rules), RULES_TYPE); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/responses/PushResponse.java: -------------------------------------------------------------------------------- 1 | package org.restonfire.responses; 2 | 3 | /** 4 | * Simple Pojo for serialization/deseriazation of the Firebase push responses for REST requests. 5 | */ 6 | public final class PushResponse { 7 | private final String name; 8 | 9 | private PushResponse() { 10 | this(""); 11 | } 12 | 13 | public PushResponse(String name) { 14 | this.name = name; 15 | } 16 | 17 | public String getName() { 18 | return name; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/responses/StreamingEvent.java: -------------------------------------------------------------------------------- 1 | package org.restonfire.responses; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.reflect.TypeToken; 5 | 6 | /** 7 | * Simple Pojo for deseriazation of the Firebase streaming event using REST. 8 | * The underlying event data can be retrieved in a serialized or deserialized form, 9 | * using the {@link Gson} instance to convert the data string. 10 | */ 11 | public class StreamingEvent { 12 | 13 | private final Gson gson; 14 | 15 | private final EventType eventType; 16 | private final String eventData; 17 | 18 | public StreamingEvent(Gson gson, EventType eventType, String eventData) { 19 | this.gson = gson; 20 | 21 | this.eventType = eventType; 22 | this.eventData = eventData; 23 | } 24 | 25 | /** 26 | * Returns the {@link EventType} for the individual event. Note that some types have been 27 | * renamed to match the corresponding {@link org.restonfire.FirebaseRestReference} interface 28 | * better. 29 | * 30 | * @return enum value for the {@link EventType} 31 | * 32 | * @see REST Streaming Documentation for more information. 33 | */ 34 | public EventType getEventType() { 35 | return eventType; 36 | } 37 | 38 | /** 39 | * Returns the raw string of the data value as it was returned by the REST API. 40 | * 41 | * @return A JSON String representing the path and the value for this event. 42 | */ 43 | public String getSerialzedEventData() { 44 | return eventData; 45 | } 46 | 47 | /** 48 | * Returns the {@link StreamingEventData} without additional type context information. This means that 49 | * {@link Gson} will make a best effort to deserialize the response. As a result, all complex types will be represented 50 | * as {@link java.util.Map}<String, Object> and {@link Integer} values will be returned as {@link Double}. 51 | * 52 | * @return The {@link StreamingEventData} object containing the relative path and the value of the event. 53 | * 54 | * @see A more detailed explanation 55 | */ 56 | public StreamingEventData getEventData() { 57 | return eventType == EventType.Set || eventType == EventType.Update 58 | ? gson.fromJson(eventData, StreamingEventData.class) 59 | : null; 60 | } 61 | 62 | /** 63 | * Returns the {@link StreamingEventData} without additional type context information. This means that 64 | * {@link Gson} will make a best effort to deserialize the response. As a result, all complex types will be represented 65 | * as {@link java.util.Map}<String, Object> and {@link Integer} values will be returned as {@link Double}. 66 | * 67 | * @param typeToken The {@link TypeToken} representing the {@link StreamingEventData} type of the data property 68 | * of the event response, including the type parameter. 69 | * 70 | * @param The type of the actual data node. 71 | * 72 | * @return The {@link StreamingEventData} object containing the relative path and the value of the event. 73 | */ 74 | public StreamingEventData getEventData(TypeToken> typeToken) { 75 | return eventType == EventType.Set || eventType == EventType.Update 76 | ? gson.>fromJson(eventData, typeToken.getType()) 77 | : null; 78 | } 79 | 80 | /** 81 | * Enum describing the different event types to be returned by the Firebase streaming service. 82 | * 83 | * @see REST Streaming Documentation for more information. 84 | */ 85 | public enum EventType { 86 | Set, 87 | Update, 88 | KeepAlive, 89 | Cancel, 90 | Expired 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/org/restonfire/responses/StreamingEventData.java: -------------------------------------------------------------------------------- 1 | package org.restonfire.responses; 2 | 3 | /** 4 | * Simple Pojo for deseriazation of the Firebase REST event data. 5 | * @param The type for the actual data behind the node. 6 | */ 7 | public final class StreamingEventData { 8 | private final String path; 9 | private final T data; 10 | 11 | public StreamingEventData() { 12 | this(null, null); 13 | } 14 | 15 | public StreamingEventData(String path, T data) { 16 | this.path = path; 17 | this.data = data; 18 | } 19 | 20 | public String getPath() { 21 | return path; 22 | } 23 | 24 | public T getData() { 25 | return data; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/org/restonfire/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j-fischer/rest-on-fire/2e264b14ed0cbff156d2a625b139fc7c2df4bd56/src/main/resources/org/restonfire/.gitkeep -------------------------------------------------------------------------------- /src/test/java/org/restonfire/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j-fischer/rest-on-fire/2e264b14ed0cbff156d2a625b139fc7c2df4bd56/src/test/java/org/restonfire/.gitkeep -------------------------------------------------------------------------------- /src/test/java/org/restonfire/BaseFirebaseRestDatabaseFactoryTest.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.ning.http.client.AsyncHttpClient; 6 | import org.junit.Test; 7 | import org.restonfire.testutils.AbstractMockTestCase; 8 | 9 | import static org.junit.Assert.assertEquals; 10 | 11 | /** 12 | * Test class for BaseFirebaseRestNamespaceFactory. 13 | */ 14 | public class BaseFirebaseRestDatabaseFactoryTest extends AbstractMockTestCase { 15 | 16 | private final AsyncHttpClient asyncHttpClient = mock(AsyncHttpClient.class); 17 | 18 | private final Gson gson = new GsonBuilder().create(); 19 | 20 | private final String path = "foo/bar"; 21 | private final String fbBaseUrl = "https://mynamespace.firebaseio.com"; 22 | private final String fbAccessToken = "someAccessToken"; 23 | 24 | private final FirebaseRestDatabaseFactory factory = new BaseFirebaseRestDatabaseFactory(asyncHttpClient, gson); 25 | 26 | @Test 27 | public void testGetReference_withAccessToken() { 28 | executeCreateTest(fbBaseUrl, fbAccessToken); 29 | } 30 | 31 | @Test 32 | public void testGetReference_withoutAccessToken() { 33 | executeCreateTest(fbBaseUrl, null); 34 | } 35 | 36 | private void executeCreateTest(String fbBaseUrl, String fbAccessToken) { 37 | FirebaseRestDatabase namespace = factory.create(fbBaseUrl, fbAccessToken); 38 | 39 | FirebaseRestReference result = namespace.getReference(path); 40 | 41 | assertEquals(this.fbBaseUrl + PathUtil.FORWARD_SLASH + path, result.getReferenceUrl()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/org/restonfire/FirebaseRestDatabaseImplTest.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.ning.http.client.AsyncHttpClient; 6 | import org.jmock.Expectations; 7 | import org.junit.Test; 8 | import org.restonfire.testutils.AbstractMockTestCase; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | import static org.junit.Assert.assertNotNull; 12 | 13 | /** 14 | * Test class for FirebaseRestNamespaceImpl. 15 | */ 16 | public class FirebaseRestDatabaseImplTest extends AbstractMockTestCase { 17 | 18 | private final AsyncHttpClient asyncHttpClient = mock(AsyncHttpClient.class); 19 | private final AsyncHttpClient.BoundRequestBuilder requestBuilder = mock(AsyncHttpClient.BoundRequestBuilder.class); 20 | 21 | private final Gson gson = new GsonBuilder().create(); 22 | 23 | private final String path = "foo/bar"; 24 | private final String fbBaseUrl = "https://mynamespace.firebaseio.com"; 25 | private final String fbAccessToken = "someAccessToken"; 26 | 27 | private final FirebaseRestDatabaseImpl namespace = new FirebaseRestDatabaseImpl(asyncHttpClient, gson, fbBaseUrl, fbAccessToken); 28 | 29 | @Test 30 | public void testGetReference() { 31 | FirebaseRestReference result = namespace.getReference(path); 32 | 33 | assertNotNull(result); 34 | assertEquals(fbBaseUrl + PathUtil.FORWARD_SLASH + path, result.getReferenceUrl()); 35 | } 36 | 37 | @Test 38 | public void testGetEventStream() { 39 | addExpectations(new Expectations() {{ 40 | oneOf(asyncHttpClient).prepareGet("https://mynamespace.firebaseio.com/foo/bar.json"); will(returnValue(requestBuilder)); 41 | oneOf(requestBuilder).addHeader("Accept", "text/event-stream"); will(returnValue(requestBuilder)); 42 | oneOf(requestBuilder).addQueryParam("auth", fbAccessToken); will(returnValue(requestBuilder)); 43 | oneOf(requestBuilder).setFollowRedirects(true); will(returnValue(requestBuilder)); 44 | }}); 45 | 46 | FirebaseRestEventStream result = namespace.getEventStream(path); 47 | 48 | assertNotNull(result); 49 | assertEquals(fbBaseUrl + PathUtil.FORWARD_SLASH + path, result.getReferenceUrl()); 50 | } 51 | 52 | @Test 53 | public void testGetSecurityRules() { 54 | FirebaseSecurityRulesReference result = namespace.getSecurityRules(); 55 | 56 | assertNotNull(result); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/org/restonfire/FirebaseRestQueryImplTest.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.ning.http.client.AsyncCompletionHandler; 6 | import com.ning.http.client.AsyncHttpClient; 7 | import com.ning.http.client.Param; 8 | import com.ning.http.client.Response; 9 | import com.ning.http.client.uri.Uri; 10 | import org.apache.commons.lang3.mutable.MutableObject; 11 | import org.jdeferred.DoneCallback; 12 | import org.jdeferred.FailCallback; 13 | import org.jdeferred.Promise; 14 | import org.jmock.Expectations; 15 | import org.junit.Test; 16 | import org.restonfire.exceptions.FirebaseAccessException; 17 | import org.restonfire.exceptions.FirebaseRestException; 18 | import org.restonfire.exceptions.FirebaseRuntimeException; 19 | import org.restonfire.testdata.SampleData; 20 | import org.restonfire.testutils.AbstractMockTestCase; 21 | import org.restonfire.testutils.MockObjectHelper; 22 | 23 | import java.io.IOException; 24 | import java.net.HttpURLConnection; 25 | import java.util.Arrays; 26 | 27 | import static org.junit.Assert.assertEquals; 28 | import static org.junit.Assert.fail; 29 | 30 | /** 31 | * Unit tests for FirebaseRestQueryImpl. 32 | */ 33 | public class FirebaseRestQueryImplTest extends AbstractMockTestCase { 34 | 35 | private final AsyncHttpClient.BoundRequestBuilder requestBuilder = mock(AsyncHttpClient.BoundRequestBuilder.class); 36 | 37 | private final Gson gson = new GsonBuilder().create(); 38 | private final MutableObject> capturedCompletionHandler = new MutableObject<>(); 39 | 40 | private final String referenceUrl = "https://mynamespace.firebaseio.com/some/path"; 41 | private final SampleData sampleData = new SampleData("foobar", 123); 42 | 43 | private final FirebaseRestQueryImpl query = new FirebaseRestQueryImpl(gson, requestBuilder, referenceUrl); 44 | 45 | @Test 46 | public void testStartAt_StringValue() { 47 | String someVal = "abc"; 48 | 49 | query.startAt(someVal); 50 | 51 | expectRequestexecution(new Param("startAt", gson.toJson(someVal))); 52 | 53 | query.run(String.class); 54 | } 55 | 56 | @Test 57 | public void testStartAt_BooleanValue() { 58 | query.startAt(true); 59 | 60 | expectRequestexecution(new Param("startAt", gson.toJson(true))); 61 | 62 | query.run(Boolean.class); 63 | } 64 | 65 | @Test 66 | public void testEndAt_StringValue() { 67 | String someVal = "abc"; 68 | 69 | query.endAt(someVal); 70 | 71 | expectRequestexecution(new Param("endAt", gson.toJson(someVal))); 72 | 73 | query.run(String.class); 74 | } 75 | 76 | @Test 77 | public void testEndAt_BooleanValue() { 78 | query.endAt(true); 79 | 80 | expectRequestexecution(new Param("endAt", gson.toJson(true))); 81 | 82 | query.run(Boolean.class); 83 | } 84 | 85 | @Test 86 | public void testEqualTo_StringValue() { 87 | String someVal = "abc"; 88 | 89 | query.equalTo(someVal); 90 | 91 | expectRequestexecution(new Param("equalTo", gson.toJson(someVal))); 92 | 93 | query.run(String.class); 94 | } 95 | 96 | @Test 97 | public void testEqualTo_BooleanValue() { 98 | query.equalTo(true); 99 | 100 | expectRequestexecution(new Param("equalTo", gson.toJson(true))); 101 | 102 | query.run(Boolean.class); 103 | } 104 | 105 | @Test 106 | public void testLimitToFirst() { 107 | query.limitToFirst(3); 108 | 109 | expectRequestexecution(new Param("limitToFirst", gson.toJson(3))); 110 | 111 | query.run(String.class); 112 | } 113 | 114 | @Test 115 | public void testLimitToLast() { 116 | query.limitToLast(5); 117 | 118 | expectRequestexecution(new Param("limitToLast", gson.toJson(5))); 119 | 120 | query.run(String.class); 121 | } 122 | 123 | @Test 124 | public void testOrderByChild() { 125 | final String childName = "foo"; 126 | 127 | query.orderByChild(childName); 128 | 129 | expectRequestexecution(new Param("orderBy", gson.toJson(childName))); 130 | 131 | query.run(String.class); 132 | } 133 | 134 | @Test 135 | public void testOrderByKey() { 136 | query.orderByKey(); 137 | 138 | expectRequestexecution(new Param("orderBy", gson.toJson("$key"))); 139 | 140 | query.run(String.class); 141 | } 142 | 143 | @Test 144 | public void testOrderByPriority() { 145 | query.orderByPriority(); 146 | 147 | expectRequestexecution(new Param("orderBy", gson.toJson("$priority"))); 148 | 149 | query.run(String.class); 150 | } 151 | 152 | @Test 153 | public void testOrderByValue() { 154 | query.orderByValue(); 155 | 156 | expectRequestexecution(new Param("orderBy", gson.toJson("$value"))); 157 | 158 | query.run(String.class); 159 | } 160 | 161 | @Test 162 | public void testFullQuery() { 163 | query 164 | .orderByValue() 165 | .startAt("bar") 166 | .endAt("foo") 167 | .limitToFirst(10); 168 | 169 | expectRequestexecution( 170 | new Param("orderBy", gson.toJson("$value")), 171 | new Param("limitToFirst", gson.toJson(10)), 172 | new Param("startAt", gson.toJson("bar")), 173 | new Param("endAt", gson.toJson("foo")) 174 | ); 175 | 176 | query.run(String.class); 177 | } 178 | 179 | @Test 180 | public void testRunQuery_success() throws Exception { 181 | final SampleData expectedSampleData = new SampleData("aValue", 123); 182 | 183 | expectGetRequest(); 184 | 185 | Promise result = query.run(SampleData.class); 186 | 187 | result.then(new DoneCallback() { 188 | @Override 189 | public void onDone(SampleData result) { 190 | assertEquals(expectedSampleData, result); 191 | } 192 | }).fail(new FailCallback() { 193 | @Override 194 | public void onFail(FirebaseRuntimeException result) { 195 | fail("The promise should not have been rejected"); 196 | } 197 | }); 198 | 199 | Response response = createResponse(referenceUrl, HttpURLConnection.HTTP_OK, gson.toJson(expectedSampleData)); 200 | 201 | capturedCompletionHandler.getValue().onCompleted(response); 202 | } 203 | 204 | @Test 205 | public void testRunQuery_forbidden() throws Exception { 206 | expectGetRequest(); 207 | executedForbiddenRequestTest(query.run(SampleData.class)); 208 | } 209 | 210 | @Test 211 | public void testGetValue_unauthorized() throws Exception { 212 | expectGetRequest(); 213 | executedUnauthorizedRequestTest(query.run(SampleData.class)); 214 | } 215 | 216 | @Test 217 | public void testGetValue_unsupportedStatusCode() throws Exception { 218 | expectGetRequest(); 219 | executedRequestWithUnsupportedResponseTest(query.run(SampleData.class), HttpURLConnection.HTTP_GATEWAY_TIMEOUT); 220 | 221 | expectGetRequest(); 222 | executedRequestWithUnsupportedResponseTest(query.run(SampleData.class), HttpURLConnection.HTTP_INTERNAL_ERROR); 223 | 224 | expectGetRequest(); 225 | executedRequestWithUnsupportedResponseTest(query.run(SampleData.class), HttpURLConnection.HTTP_NOT_FOUND); 226 | } 227 | 228 | @Test 229 | public void testClear() { 230 | // This will set the state of the query to contain multiple filters. 231 | testFullQuery(); 232 | assertIsSatisfied(); 233 | 234 | //Re-running the query should set the same filters 235 | expectRequestexecution( 236 | new Param("orderBy", gson.toJson("$value")), 237 | new Param("limitToFirst", gson.toJson(10)), 238 | new Param("startAt", gson.toJson("bar")), 239 | new Param("endAt", gson.toJson("foo")) 240 | ); 241 | 242 | query.run(String.class); 243 | assertIsSatisfied(); 244 | 245 | query.clear(); 246 | 247 | //No, all filters have been removed. 248 | expectGetRequest(); 249 | query.run(String.class); 250 | } 251 | 252 | private void executedRequestWithUnsupportedResponseTest(Promise result, int responseCode) throws Exception { 253 | executedFailedRequestTest(result, responseCode, FirebaseRestException.class, null, FirebaseRuntimeException.ErrorCode.UnsupportedStatusCode); 254 | } 255 | 256 | private void executedForbiddenRequestTest(Promise result) throws Exception { 257 | executedFailedRequestTest(result, HttpURLConnection.HTTP_FORBIDDEN, FirebaseAccessException.class, null, FirebaseRuntimeException.ErrorCode.AccessViolation); 258 | } 259 | 260 | private void executedUnauthorizedRequestTest(Promise result) throws Exception { 261 | executedFailedRequestTest(result, HttpURLConnection.HTTP_UNAUTHORIZED, FirebaseAccessException.class, null, FirebaseRuntimeException.ErrorCode.AccessViolation); 262 | } 263 | 264 | private void executedFailedRequestTest(Promise result, int statusCode, final Class exceptionClazz, String requestBody, final FirebaseRuntimeException.ErrorCode expectedErrorCode) throws Exception { 265 | result.then(new DoneCallback() { 266 | @Override 267 | public void onDone(TResult result) { 268 | fail("The promise should not have been resolved"); 269 | } 270 | }).fail(new FailCallback() { 271 | @Override 272 | public void onFail(FirebaseRuntimeException result) { 273 | assertEquals(exceptionClazz, result.getClass()); 274 | assertEquals(expectedErrorCode, result.getErrorCode()); 275 | } 276 | }); 277 | 278 | Response response = createResponse(referenceUrl, statusCode, requestBody); 279 | 280 | capturedCompletionHandler.getValue().onCompleted(response); 281 | 282 | assertIsSatisfied(); 283 | } 284 | 285 | private void expectGetRequest() { 286 | addExpectations(new Expectations() {{ 287 | oneOf(requestBuilder).execute(with(aNonNull(AsyncCompletionHandler.class))); will(MockObjectHelper.capture(capturedCompletionHandler)); 288 | }}); 289 | } 290 | 291 | private Response createResponse(final String url, final int statusCode, final String responseBody) throws IOException { 292 | final Response response = mock(Response.class, String.format("Response(%s, %d)", url, statusCode)); 293 | addExpectations(new Expectations() {{ 294 | allowing(response).getUri(); will(returnValue(Uri.create(url))); 295 | allowing(response).getStatusCode(); will(returnValue(statusCode)); 296 | allowing(response).getResponseBody(); will(returnValue(responseBody)); 297 | }}); 298 | 299 | return response; 300 | } 301 | 302 | private void expectRequestexecution(Param... params) { 303 | expectParams(params); 304 | 305 | addExpectations(new Expectations() {{ 306 | oneOf(requestBuilder).execute(with(any(AsyncCompletionHandler.class))); will(MockObjectHelper.capture(capturedCompletionHandler)); 307 | }}); 308 | } 309 | 310 | private void expectParams(final Param[] params) { 311 | addExpectations(new Expectations() {{ 312 | oneOf(requestBuilder).addQueryParams(with(MockObjectHelper.elementsAreEqual(Arrays.asList(params)))); 313 | }}); 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /src/test/java/org/restonfire/FirebaseSecurityRulesReferenceImplTest.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.ning.http.client.AsyncCompletionHandler; 6 | import com.ning.http.client.AsyncHttpClient; 7 | import com.ning.http.client.Response; 8 | import com.ning.http.client.uri.Uri; 9 | import org.apache.commons.lang3.mutable.MutableObject; 10 | import org.jdeferred.DoneCallback; 11 | import org.jdeferred.FailCallback; 12 | import org.jdeferred.Promise; 13 | import org.jmock.Expectations; 14 | import org.junit.Test; 15 | import org.restonfire.exceptions.FirebaseAccessException; 16 | import org.restonfire.exceptions.FirebaseRestException; 17 | import org.restonfire.exceptions.FirebaseRuntimeException; 18 | import org.restonfire.responses.FirebaseSecurityRules; 19 | import org.restonfire.testutils.AbstractMockTestCase; 20 | import org.restonfire.testutils.MockObjectHelper; 21 | 22 | import java.io.IOException; 23 | import java.net.HttpURLConnection; 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | 27 | import static org.junit.Assert.*; 28 | 29 | /** 30 | * Unit tests for FirebaseSecurityRulesReferenceImpl. 31 | */ 32 | public class FirebaseSecurityRulesReferenceImplTest extends AbstractMockTestCase { 33 | 34 | private final AsyncHttpClient asyncHttpClient = mock(AsyncHttpClient.class); 35 | private final AsyncHttpClient.BoundRequestBuilder requestBuilder = mock(AsyncHttpClient.BoundRequestBuilder.class); 36 | 37 | private final Gson gson = new GsonBuilder().create(); 38 | private final String fbBaseUrl = "https://mynamespace.firebaseio.com"; 39 | private final FirebaseSecurityRules sampleRules = new FirebaseSecurityRules(); 40 | 41 | private final MutableObject> capturedCompletionHandler = new MutableObject<>(); 42 | 43 | private final FirebaseSecurityRulesReferenceImpl ref = new FirebaseSecurityRulesReferenceImpl( 44 | asyncHttpClient, 45 | fbBaseUrl, 46 | null 47 | ); 48 | 49 | @Test 50 | public void testGet_forbidden() throws Exception { 51 | expectGetRequest(); 52 | executedForbiddenRequestTest(ref.get()); 53 | } 54 | 55 | @Test 56 | public void testGet_unauthorized() throws Exception { 57 | expectGetRequest(); 58 | executedUnauthorizedRequestTest(ref.get()); 59 | } 60 | 61 | @Test 62 | public void testGetValue_unsupportedStatusCode() throws Exception { 63 | expectGetRequest(); 64 | executedRequestWithUnsupportedResponseTest(ref.get(), HttpURLConnection.HTTP_GATEWAY_TIMEOUT); 65 | 66 | expectGetRequest(); 67 | executedRequestWithUnsupportedResponseTest(ref.get(), HttpURLConnection.HTTP_INTERNAL_ERROR); 68 | 69 | expectGetRequest(); 70 | executedRequestWithUnsupportedResponseTest(ref.get(), HttpURLConnection.HTTP_NOT_FOUND); 71 | } 72 | 73 | @Test 74 | public void testGet_success() throws Exception { 75 | final Map sampleRules = getSampleRules(); 76 | 77 | final FirebaseSecurityRules expectedSecurityRules = new FirebaseSecurityRules( 78 | sampleRules 79 | ); 80 | 81 | expectGetRequest(); 82 | 83 | Promise result = ref.get(); 84 | 85 | result.then(new DoneCallback() { 86 | @Override 87 | public void onDone(FirebaseSecurityRules result) { 88 | assertEquals(sampleRules.get(".read"), result.getRules().get(".read")); 89 | assertEquals(sampleRules.get(".write"), result.getRules().get(".write")); 90 | } 91 | }).fail(new FailCallback() { 92 | @Override 93 | public void onFail(FirebaseRuntimeException result) { 94 | fail("The promise should not have been rejected"); 95 | } 96 | }); 97 | 98 | Response response = createResponse(getFirebaseRestUrl(), HttpURLConnection.HTTP_OK, gson.toJson(expectedSecurityRules)); 99 | 100 | capturedCompletionHandler.getValue().onCompleted(response); 101 | } 102 | 103 | @Test 104 | public void testSetValue_forbidden() throws Exception { 105 | expectSetRequest(sampleRules); 106 | 107 | executedForbiddenRequestTest(ref.set(sampleRules)); 108 | } 109 | 110 | @Test 111 | public void testSetValue_unauthorized() throws Exception { 112 | expectSetRequest(sampleRules); 113 | 114 | executedUnauthorizedRequestTest(ref.set(sampleRules)); 115 | } 116 | 117 | @Test 118 | public void testSetValue_unsupportedStatusCode() throws Exception { 119 | expectSetRequest(sampleRules); 120 | executedRequestWithUnsupportedResponseTest(ref.set(sampleRules), HttpURLConnection.HTTP_GATEWAY_TIMEOUT); 121 | 122 | expectSetRequest(sampleRules); 123 | executedRequestWithUnsupportedResponseTest(ref.set(sampleRules), HttpURLConnection.HTTP_INTERNAL_ERROR); 124 | 125 | expectSetRequest(sampleRules); 126 | executedRequestWithUnsupportedResponseTest(ref.set(sampleRules), HttpURLConnection.HTTP_NOT_FOUND); 127 | } 128 | 129 | @Test 130 | public void testSetValue_success() throws Exception { 131 | expectSetRequest(sampleRules); 132 | 133 | Promise result = ref.set(sampleRules); 134 | 135 | result.then(new DoneCallback() { 136 | @Override 137 | public void onDone(FirebaseSecurityRules result) { 138 | assertSame(sampleRules, result); 139 | } 140 | }).fail(new FailCallback() { 141 | @Override 142 | public void onFail(FirebaseRuntimeException result) { 143 | fail("The promise should not have been rejected"); 144 | } 145 | }); 146 | 147 | Response response = createResponse(fbBaseUrl, HttpURLConnection.HTTP_OK, gson.toJson(sampleRules)); 148 | 149 | capturedCompletionHandler.getValue().onCompleted(response); 150 | } 151 | 152 | private void executedRequestWithUnsupportedResponseTest(Promise result, int responseCode) throws Exception { 153 | executedFailedRequestTest(result, responseCode, FirebaseRestException.class, null, FirebaseRuntimeException.ErrorCode.UnsupportedStatusCode); 154 | } 155 | 156 | private void executedForbiddenRequestTest(Promise result) throws Exception { 157 | executedFailedRequestTest(result, HttpURLConnection.HTTP_FORBIDDEN, FirebaseAccessException.class, null, FirebaseRuntimeException.ErrorCode.AccessViolation); 158 | } 159 | 160 | private void executedUnauthorizedRequestTest(Promise result) throws Exception { 161 | executedFailedRequestTest(result, HttpURLConnection.HTTP_UNAUTHORIZED, FirebaseAccessException.class, null, FirebaseRuntimeException.ErrorCode.AccessViolation); 162 | } 163 | 164 | private void executedFailedRequestTest(Promise result, int statusCode, final Class exceptionClazz, String requestBody, final FirebaseRuntimeException.ErrorCode expectedErrorCode) throws Exception { 165 | result.then(new DoneCallback() { 166 | @Override 167 | public void onDone(FirebaseSecurityRules result) { 168 | fail("The promise should not have been resolved"); 169 | } 170 | }).fail(new FailCallback() { 171 | @Override 172 | public void onFail(FirebaseRuntimeException result) { 173 | assertEquals(exceptionClazz, result.getClass()); 174 | assertEquals(expectedErrorCode, result.getErrorCode()); 175 | } 176 | }); 177 | 178 | Response response = createResponse(getFirebaseRestUrl(), statusCode, requestBody); 179 | 180 | capturedCompletionHandler.getValue().onCompleted(response); 181 | 182 | assertIsSatisfied(); 183 | } 184 | 185 | private void expectSetRequest(final T data) { 186 | addExpectations(new Expectations() {{ 187 | oneOf(asyncHttpClient).preparePut(getFirebaseRestUrl()); will(returnValue(requestBuilder)); 188 | oneOf(requestBuilder).setBody(gson.toJson(data)); will(returnValue(requestBuilder)); 189 | oneOf(requestBuilder).execute(with(aNonNull(AsyncCompletionHandler.class))); will(MockObjectHelper.capture(capturedCompletionHandler)); 190 | }}); 191 | } 192 | 193 | private void expectGetRequest() { 194 | addExpectations(new Expectations() {{ 195 | oneOf(asyncHttpClient).prepareGet(getFirebaseRestUrl()); will(returnValue(requestBuilder)); 196 | oneOf(requestBuilder).execute(with(aNonNull(AsyncCompletionHandler.class))); will(MockObjectHelper.capture(capturedCompletionHandler)); 197 | }}); 198 | } 199 | 200 | private Response createResponse(final String url, final int statusCode, final String responseBody) throws IOException { 201 | final Response response = mock(Response.class, String.format("Response(%s, %d)", url, statusCode)); 202 | addExpectations(new Expectations() {{ 203 | allowing(response).getUri(); will(returnValue(Uri.create(url))); 204 | allowing(response).getStatusCode(); will(returnValue(statusCode)); 205 | allowing(response).getResponseBody(); will(returnValue(responseBody)); 206 | }}); 207 | 208 | return response; 209 | } 210 | 211 | private Map getSampleRules() { 212 | HashMap rules = new HashMap<>(); 213 | rules.put(".read", true); 214 | rules.put(".write", false); 215 | 216 | return rules; 217 | } 218 | 219 | private String getFirebaseRestUrl() { 220 | return fbBaseUrl + "/.settings/rules" + FirebaseDocumentLocation.JSON_SUFFIX; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/test/java/org/restonfire/PathUtilTest.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | 9 | /** 10 | * Test class for PathUtil. 11 | */ 12 | public class PathUtilTest { 13 | 14 | private static final String PATH = "foo/bar"; 15 | private static final String PATH_WITH_SLASH = "foo/bar/"; 16 | 17 | private static final String SINGLE_PATH = "foo"; 18 | 19 | @Rule 20 | public ExpectedException exception = ExpectedException.none(); 21 | 22 | @Test 23 | public void getParent_pathIsNull() throws Exception { 24 | exception.expect(IllegalArgumentException.class); 25 | exception.expectMessage("path cannot be null"); 26 | 27 | PathUtil.getParent(null); 28 | } 29 | 30 | @Test 31 | public void getParent_pathIsEmptyString() throws Exception { 32 | assertEquals(null, PathUtil.getParent("")); 33 | } 34 | 35 | @Test 36 | public void getParent_pathIsSingleSlash() throws Exception { 37 | assertEquals(null, PathUtil.getParent("/")); 38 | } 39 | 40 | @Test 41 | public void getParent_singleCharPath() throws Exception { 42 | assertEquals("", PathUtil.getParent("a")); 43 | } 44 | 45 | @Test 46 | public void getParent_singleCharPathWithSlash() throws Exception { 47 | assertEquals("", PathUtil.getParent("a/")); 48 | } 49 | 50 | @Test 51 | public void getParent_pathEndsWithSlash() throws Exception { 52 | assertEquals("foo", PathUtil.getParent(PATH_WITH_SLASH)); 53 | } 54 | 55 | @Test 56 | public void getParent_pathDoesNotEndWithSlash() throws Exception { 57 | assertEquals("foo", PathUtil.getParent(PATH)); 58 | } 59 | 60 | @Test 61 | public void getChild_pathIsNull() throws Exception { 62 | exception.expect(IllegalArgumentException.class); 63 | exception.expectMessage("path cannot be null"); 64 | 65 | PathUtil.concatenatePath(null, SINGLE_PATH); 66 | } 67 | 68 | @Test 69 | public void getChild_ofRootPath() throws Exception { 70 | assertEquals("foo", PathUtil.concatenatePath("", SINGLE_PATH)); 71 | } 72 | 73 | @Test 74 | public void getChild_ofRootPathWithSlash() throws Exception { 75 | assertEquals("foo", PathUtil.concatenatePath("/", SINGLE_PATH)); 76 | } 77 | 78 | @Test 79 | public void getChild_ofExistingPath() throws Exception { 80 | assertEquals("foo/bar/foo", PathUtil.concatenatePath(PATH, SINGLE_PATH)); 81 | } 82 | 83 | @Test 84 | public void getChild_ofExistingPathWithSlash() throws Exception { 85 | assertEquals("foo/bar/foo", PathUtil.concatenatePath(PATH_WITH_SLASH, SINGLE_PATH)); 86 | } 87 | 88 | @Test 89 | public void getChild_ofExistingPathWithWithMultileveChild() throws Exception { 90 | assertEquals("foo/bar/foo/bar", PathUtil.concatenatePath(PATH, PATH)); 91 | assertEquals("foo/bar/foo/bar", PathUtil.concatenatePath(PATH_WITH_SLASH, PATH_WITH_SLASH)); 92 | } 93 | 94 | @Test 95 | public void normalizePath() { 96 | assertEquals(PATH, PathUtil.normalizePath(PATH)); 97 | assertEquals(PATH, PathUtil.normalizePath(PATH_WITH_SLASH)); 98 | } 99 | } -------------------------------------------------------------------------------- /src/test/java/org/restonfire/RequestBuilderUtilTest.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | import com.ning.http.client.AsyncHttpClient; 4 | import org.jmock.Expectations; 5 | import org.junit.Test; 6 | import org.restonfire.RequestBuilderUtil; 7 | import org.restonfire.testutils.AbstractMockTestCase; 8 | 9 | import static org.junit.Assert.assertSame; 10 | 11 | /** 12 | * Test class for RequestBuilderUtil. 13 | */ 14 | public class RequestBuilderUtilTest extends AbstractMockTestCase { 15 | 16 | private final AsyncHttpClient asyncHttpClient = mock(AsyncHttpClient.class); 17 | private final AsyncHttpClient.BoundRequestBuilder requestBuilder = mock(AsyncHttpClient.BoundRequestBuilder.class); 18 | 19 | private final String referenceUrl = "https://www.some-domain.com"; 20 | private final String fbAccessToken = "someAccessToken"; 21 | private final String body = "some random body"; 22 | 23 | @Test 24 | public void testCreateGet() { 25 | addExpectations(new Expectations() {{ 26 | oneOf(asyncHttpClient).prepareGet(referenceUrl); will(returnValue(requestBuilder)); 27 | oneOf(requestBuilder).addQueryParam("auth", fbAccessToken); will(returnValue(requestBuilder)); 28 | }}); 29 | 30 | assertSame(requestBuilder, RequestBuilderUtil.createGet(asyncHttpClient, referenceUrl, fbAccessToken)); 31 | } 32 | 33 | @Test 34 | public void testCreateGet_noAccessToken() { 35 | addExpectations(new Expectations() {{ 36 | oneOf(asyncHttpClient).prepareGet(referenceUrl); will(returnValue(requestBuilder)); 37 | }}); 38 | 39 | assertSame(requestBuilder, RequestBuilderUtil.createGet(asyncHttpClient, referenceUrl, null)); 40 | } 41 | 42 | @Test 43 | public void testCreateGet_emptyAccessToken() { 44 | addExpectations(new Expectations() {{ 45 | oneOf(asyncHttpClient).prepareGet(referenceUrl); will(returnValue(requestBuilder)); 46 | }}); 47 | 48 | assertSame(requestBuilder, RequestBuilderUtil.createGet(asyncHttpClient, referenceUrl, "")); 49 | } 50 | 51 | @Test 52 | public void testCreatePost() { 53 | addExpectations(new Expectations() {{ 54 | oneOf(asyncHttpClient).preparePost(referenceUrl); will(returnValue(requestBuilder)); 55 | oneOf(requestBuilder).setBody(body); will(returnValue(requestBuilder)); 56 | oneOf(requestBuilder).addQueryParam("auth", fbAccessToken); will(returnValue(requestBuilder)); 57 | }}); 58 | 59 | assertSame(requestBuilder, RequestBuilderUtil.createPost(asyncHttpClient, referenceUrl, fbAccessToken, body)); 60 | } 61 | 62 | @Test 63 | public void testCreatePost_noAccessToken() { 64 | addExpectations(new Expectations() {{ 65 | oneOf(asyncHttpClient).preparePost(referenceUrl); will(returnValue(requestBuilder)); 66 | oneOf(requestBuilder).setBody(body); will(returnValue(requestBuilder)); 67 | }}); 68 | 69 | assertSame(requestBuilder, RequestBuilderUtil.createPost(asyncHttpClient, referenceUrl, null, body)); 70 | } 71 | 72 | @Test 73 | public void testCreatePatch() { 74 | addExpectations(new Expectations() {{ 75 | oneOf(asyncHttpClient).preparePatch(referenceUrl); will(returnValue(requestBuilder)); 76 | oneOf(requestBuilder).setBody(body); will(returnValue(requestBuilder)); 77 | oneOf(requestBuilder).addQueryParam("auth", fbAccessToken); will(returnValue(requestBuilder)); 78 | }}); 79 | 80 | assertSame(requestBuilder, RequestBuilderUtil.createPatch(asyncHttpClient, referenceUrl, fbAccessToken, body)); 81 | } 82 | 83 | @Test 84 | public void testCreatePatch_noAccessToken() { 85 | addExpectations(new Expectations() {{ 86 | oneOf(asyncHttpClient).preparePatch(referenceUrl); will(returnValue(requestBuilder)); 87 | oneOf(requestBuilder).setBody(body); will(returnValue(requestBuilder)); 88 | }}); 89 | 90 | assertSame(requestBuilder, RequestBuilderUtil.createPatch(asyncHttpClient, referenceUrl, null, body)); 91 | } 92 | 93 | @Test 94 | public void testCreatePut() { 95 | addExpectations(new Expectations() {{ 96 | oneOf(asyncHttpClient).preparePut(referenceUrl); will(returnValue(requestBuilder)); 97 | oneOf(requestBuilder).setBody(body); will(returnValue(requestBuilder)); 98 | oneOf(requestBuilder).addQueryParam("auth", fbAccessToken); will(returnValue(requestBuilder)); 99 | }}); 100 | 101 | assertSame(requestBuilder, RequestBuilderUtil.createPut(asyncHttpClient, referenceUrl, fbAccessToken, body)); 102 | } 103 | 104 | @Test 105 | public void testCreatePut_noAccessToken() { 106 | addExpectations(new Expectations() {{ 107 | oneOf(asyncHttpClient).preparePut(referenceUrl); will(returnValue(requestBuilder)); 108 | oneOf(requestBuilder).setBody(body); will(returnValue(requestBuilder)); 109 | }}); 110 | 111 | assertSame(requestBuilder, RequestBuilderUtil.createPut(asyncHttpClient, referenceUrl, null, body)); 112 | } 113 | 114 | @Test 115 | public void testCreateDelete() { 116 | addExpectations(new Expectations() {{ 117 | oneOf(asyncHttpClient).prepareDelete(referenceUrl); will(returnValue(requestBuilder)); 118 | oneOf(requestBuilder).addQueryParam("auth", fbAccessToken); will(returnValue(requestBuilder)); 119 | }}); 120 | 121 | assertSame(requestBuilder, RequestBuilderUtil.createDelete(asyncHttpClient, referenceUrl, fbAccessToken)); 122 | } 123 | 124 | @Test 125 | public void testCreateDelete_noAccessToken() { 126 | addExpectations(new Expectations() {{ 127 | oneOf(asyncHttpClient).prepareDelete(referenceUrl); will(returnValue(requestBuilder)); 128 | }}); 129 | 130 | assertSame(requestBuilder, RequestBuilderUtil.createDelete(asyncHttpClient, referenceUrl, null)); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/test/java/org/restonfire/StringUtilTest.java: -------------------------------------------------------------------------------- 1 | package org.restonfire; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertFalse; 6 | import static org.junit.Assert.assertTrue; 7 | 8 | /** 9 | * Test class for StringUtil. 10 | */ 11 | public class StringUtilTest { 12 | 13 | @Test 14 | public void testNotNullOrEmpty_null() { 15 | assertFalse(StringUtil.notNullOrEmpty(null)); 16 | } 17 | 18 | @Test 19 | public void testNotNullOrEmpty_empty() { 20 | assertFalse(StringUtil.notNullOrEmpty("")); 21 | } 22 | 23 | @Test 24 | public void testNotNullOrEmpty_notEmpty() { 25 | assertTrue(StringUtil.notNullOrEmpty("a")); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/org/restonfire/fakes/FakeResponseHeaders.java: -------------------------------------------------------------------------------- 1 | package org.restonfire.fakes; 2 | 3 | import com.ning.http.client.FluentCaseInsensitiveStringsMap; 4 | import com.ning.http.client.HttpResponseHeaders; 5 | import org.apache.commons.lang3.NotImplementedException; 6 | 7 | /** 8 | * Mock class for HttpResponseHeaders. 9 | */ 10 | public class FakeResponseHeaders extends HttpResponseHeaders { 11 | @Override 12 | public FluentCaseInsensitiveStringsMap getHeaders() { 13 | throw new NotImplementedException("getHeaders"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/org/restonfire/fakes/FakeResponseStatus.java: -------------------------------------------------------------------------------- 1 | package org.restonfire.fakes; 2 | 3 | import com.ning.http.client.HttpResponseBodyPart; 4 | import com.ning.http.client.HttpResponseHeaders; 5 | import com.ning.http.client.HttpResponseStatus; 6 | import com.ning.http.client.Response; 7 | import com.ning.http.client.uri.Uri; 8 | import org.apache.commons.lang3.NotImplementedException; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Mock class for HttpResponseStatus. 14 | */ 15 | public class FakeResponseStatus extends HttpResponseStatus { 16 | 17 | private final int statusCode; 18 | 19 | public FakeResponseStatus(String url, int statusCode) { 20 | super(Uri.create(url), null); 21 | this.statusCode = statusCode; 22 | } 23 | 24 | @Override 25 | public int getStatusCode() { 26 | return statusCode; 27 | } 28 | 29 | @Override 30 | public Response prepareResponse(HttpResponseHeaders headers, List bodyParts) { 31 | throw new NotImplementedException("prepareResponse"); 32 | } 33 | 34 | @Override 35 | public String getStatusText() { 36 | throw new NotImplementedException("getStatusText"); 37 | } 38 | 39 | @Override 40 | public String getProtocolName() { 41 | throw new NotImplementedException("getProtocolName"); 42 | } 43 | 44 | @Override 45 | public int getProtocolMajorVersion() { 46 | throw new NotImplementedException("getProtocolMajorVersion"); 47 | } 48 | 49 | @Override 50 | public int getProtocolMinorVersion() { 51 | throw new NotImplementedException("getProtocolMinorVersion"); 52 | } 53 | 54 | @Override 55 | public String getProtocolText() { 56 | throw new NotImplementedException("getProtocolText"); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/org/restonfire/responses/StreamingEventTest.java: -------------------------------------------------------------------------------- 1 | package org.restonfire.responses; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.google.gson.reflect.TypeToken; 6 | import org.junit.Test; 7 | import org.restonfire.testdata.SampleData; 8 | 9 | import java.util.Map; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | import static org.junit.Assert.assertNull; 13 | 14 | /** 15 | * Unit tests for {@link StreamingEvent}. 16 | */ 17 | public class StreamingEventTest { 18 | 19 | private final Gson gson = new GsonBuilder().create(); 20 | 21 | private final int intVal = new Integer(4); 22 | private final double intAsDouble = (double) intVal; 23 | private final SampleData sampleData = new SampleData("foo", 2); 24 | private final Map sampleDataAsMap = sampleData.toAutoDeserializedMap(); 25 | 26 | private final String intResponseStr = "{ path: '/', data: 4 }"; 27 | private final String sampleDataString = "{ path: '/', data: " + gson.toJson(sampleData) + "}"; 28 | 29 | @Test 30 | public void testGetSerialzedEventData() { 31 | assertEquals(intResponseStr, createRespose(StreamingEvent.EventType.Set, intResponseStr).getSerialzedEventData()); 32 | assertEquals(sampleDataString, createRespose(StreamingEvent.EventType.Set, sampleDataString).getSerialzedEventData()); 33 | } 34 | 35 | @Test 36 | public void testGetEventData_set_deserializedValues() { 37 | assertEquals(intAsDouble, (Object) createRespose(StreamingEvent.EventType.Set, intResponseStr).getEventData().getData()); 38 | assertEquals(sampleDataAsMap, createRespose(StreamingEvent.EventType.Set, sampleDataString).getEventData().getData()); 39 | } 40 | 41 | @Test 42 | public void testGetEventData_update_deserializedValues() { 43 | assertEquals(intAsDouble, (Object) createRespose(StreamingEvent.EventType.Update, intResponseStr).getEventData().getData()); 44 | assertEquals(sampleDataAsMap, createRespose(StreamingEvent.EventType.Update, sampleDataString).getEventData().getData()); 45 | } 46 | 47 | @Test 48 | public void testGetEventData_typed() { 49 | assertEquals(sampleData, createRespose(StreamingEvent.EventType.Set, sampleDataString).getEventData(new TypeToken>() { }).getData()); 50 | assertEquals(sampleData, createRespose(StreamingEvent.EventType.Update, sampleDataString).getEventData(new TypeToken>() { }).getData()); 51 | } 52 | 53 | @Test 54 | public void testGetEventData_returnsNullForAllOtherEventTypes() { 55 | assertNull(createRespose(StreamingEvent.EventType.Cancel, intResponseStr).getEventData()); 56 | assertNull(createRespose(StreamingEvent.EventType.Expired, intResponseStr).getEventData()); 57 | assertNull(createRespose(StreamingEvent.EventType.KeepAlive, intResponseStr).getEventData()); 58 | } 59 | 60 | @Test 61 | public void testGetEventData_checkEventResponseDataPath() { 62 | assertEquals("/", createRespose(StreamingEvent.EventType.Update, intResponseStr).getEventData().getPath()); 63 | } 64 | 65 | 66 | private StreamingEvent createRespose(StreamingEvent.EventType eventType, String eventData) { 67 | return new StreamingEvent(gson, eventType, eventData); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/org/restonfire/testdata/SampleData.java: -------------------------------------------------------------------------------- 1 | package org.restonfire.testdata; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Simple POJO to be used as a more complex document data value. 8 | */ 9 | public class SampleData { 10 | public String aString; 11 | public int anInt; 12 | 13 | public SampleData() { 14 | // do nothing 15 | } 16 | 17 | public SampleData(String aString, int anInt) { 18 | this.aString = aString; 19 | this.anInt = anInt; 20 | } 21 | 22 | /* 23 | * This function converts this SampleData object to a Map as it would be 24 | * returned by Gson if no type parameter was provided for the deserialization. 25 | */ 26 | public Map toAutoDeserializedMap() { 27 | Map map = new HashMap<>(); 28 | map.put("aString", aString); 29 | map.put("anInt", (double) anInt); 30 | 31 | return map; 32 | } 33 | 34 | @Override 35 | public boolean equals(Object o) { 36 | if (this == o) return true; 37 | if (o == null || getClass() != o.getClass()) return false; 38 | 39 | SampleData that = (SampleData) o; 40 | 41 | if (anInt != that.anInt) return false; 42 | return aString != null ? aString.equals(that.aString) : that.aString == null; 43 | } 44 | 45 | @Override 46 | public int hashCode() { 47 | int result = aString != null ? aString.hashCode() : 0; 48 | result = 31 * result + anInt; 49 | return result; 50 | } 51 | } -------------------------------------------------------------------------------- /src/test/java/org/restonfire/testutils/AbstractMockTestCase.java: -------------------------------------------------------------------------------- 1 | package org.restonfire.testutils; 2 | 3 | import org.jmock.Expectations; 4 | import org.jmock.Mockery; 5 | import org.jmock.lib.legacy.ClassImposteriser; 6 | import org.junit.After; 7 | 8 | /** 9 | * Helper class for unit tests using mock objects. 10 | */ 11 | public abstract class AbstractMockTestCase { 12 | 13 | protected Mockery context = new Mockery() {{ 14 | setImposteriser(ClassImposteriser.INSTANCE); 15 | }}; 16 | 17 | protected T mock(Class clazz) { 18 | return context.mock(clazz); 19 | } 20 | 21 | protected T mock(Class clazz, String name) { 22 | return context.mock(clazz, name); 23 | } 24 | 25 | protected void addExpectations(Expectations expectations) { 26 | context.checking(expectations); 27 | } 28 | 29 | @After 30 | public void assertIsSatisfied() { 31 | context.assertIsSatisfied(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/org/restonfire/testutils/MockObjectHelper.java: -------------------------------------------------------------------------------- 1 | package org.restonfire.testutils; 2 | 3 | import junit.framework.AssertionFailedError; 4 | import junitx.framework.ListAssert; 5 | import org.apache.commons.lang3.mutable.MutableObject; 6 | import org.hamcrest.BaseMatcher; 7 | import org.hamcrest.Description; 8 | import org.hamcrest.Matcher; 9 | import org.jmock.api.Action; 10 | import org.jmock.api.Invocation; 11 | 12 | import java.util.Arrays; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.concurrent.Callable; 16 | 17 | /** 18 | * Helper functions to enhance the behavior of mock objects. 19 | */ 20 | public class MockObjectHelper 21 | { 22 | public static Action runRunnable() 23 | { 24 | return runRunnable(0); 25 | } 26 | 27 | public static Action runRunnable(final int argumentIndex) 28 | { 29 | return new Action() { 30 | 31 | public Object invoke(Invocation invocation) throws Throwable 32 | { 33 | final Runnable runnable = (Runnable) invocation.getParameter(argumentIndex); 34 | runnable.run(); 35 | return null; 36 | } 37 | 38 | public void describeTo(Description description) 39 | { 40 | description.appendText("Run a runnable"); 41 | } 42 | }; 43 | } 44 | 45 | public static Action callCallable() 46 | { 47 | return callCallable(0); 48 | } 49 | 50 | public static Action callCallable(final int argumentIndex) 51 | { 52 | return new Action() { 53 | 54 | public Object invoke(Invocation invocation) throws Throwable 55 | { 56 | final Callable callable = (Callable) invocation.getParameter(argumentIndex); 57 | return callable.call(); 58 | } 59 | 60 | public void describeTo(Description description) 61 | { 62 | description.appendText("Run a runnable"); 63 | } 64 | }; 65 | } 66 | 67 | public static Action capture(final MutableObject obj) 68 | { 69 | return capture(obj, 0); 70 | } 71 | 72 | public static Action capture(final MutableObject obj, int argumentIndex) 73 | { 74 | return new CaptureAction(argumentIndex, obj); 75 | } 76 | 77 | public static Action addElementsToMap(Map newElements) 78 | { 79 | return new AddElementsAction(newElements); 80 | } 81 | 82 | public static Matcher> elementsAreEqual(final List expected) { 83 | return new BaseMatcher>() { 84 | @Override 85 | public boolean matches(Object item) { 86 | List actual = (List) item; 87 | 88 | try { 89 | ListAssert.assertEquals(expected, actual); 90 | } 91 | catch (AssertionFailedError e) { 92 | return false; 93 | } 94 | return true; 95 | } 96 | 97 | @Override 98 | public void describeTo(Description description) { 99 | description.appendText("all elements should be equal to ").appendValue(Arrays.toString(expected.toArray())); 100 | } 101 | }; 102 | } 103 | 104 | private static class CaptureAction implements Action 105 | { 106 | private final MutableObject mutable; 107 | private final int argumentIndex; 108 | 109 | public CaptureAction(Integer argumentIndex, MutableObject mutable) 110 | { 111 | assert mutable != null : "mutable object cannot be null"; 112 | assert argumentIndex != null : "argument index cannot be null"; 113 | 114 | this.mutable = mutable; 115 | this.argumentIndex = argumentIndex; 116 | } 117 | 118 | public Object invoke(Invocation invocation) throws Throwable 119 | { 120 | mutable.setValue((T) invocation.getParameter(argumentIndex)); 121 | return null; 122 | } 123 | 124 | public void describeTo(Description description) 125 | { 126 | description.appendText("capture-to MutableObject"); 127 | } 128 | } 129 | 130 | private static class AddElementsAction implements Action 131 | { 132 | private Map elements; 133 | 134 | public AddElementsAction(Map elements) 135 | { 136 | this.elements = elements; 137 | } 138 | 139 | public void describeTo(Description description) 140 | { 141 | description.appendText("adds ") 142 | .appendValueList("", ", ", "", elements) 143 | .appendText(" to a collection"); 144 | } 145 | 146 | public Object invoke(Invocation invocation) throws Throwable 147 | { 148 | ((Map)invocation.getParameter(0)).putAll(elements); 149 | return null; 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | UTF-8 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/test/resources/org/restonfire/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j-fischer/rest-on-fire/2e264b14ed0cbff156d2a625b139fc7c2df4bd56/src/test/resources/org/restonfire/.gitkeep --------------------------------------------------------------------------------