├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmignore ├── CHANGES.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── DEVELOPING.md ├── LICENSE ├── README.md ├── build.gradle ├── docs.json ├── docs ├── GettingStarted.md ├── README.dist ├── Subclassing.md ├── api │ ├── allclasses-frame.html │ ├── allclasses-noframe.html │ ├── com │ │ └── strongloop │ │ │ └── android │ │ │ ├── loopback │ │ │ ├── AccessToken.html │ │ │ ├── AccessTokenRepository.html │ │ │ ├── BuildConfig.html │ │ │ ├── Container.html │ │ │ ├── ContainerRepository.html │ │ │ ├── File.DownloadCallback.html │ │ │ ├── File.html │ │ │ ├── FileRepository.html │ │ │ ├── LocalInstallation.html │ │ │ ├── Model.Callback.html │ │ │ ├── Model.html │ │ │ ├── ModelRepository.FindAllCallback.html │ │ │ ├── ModelRepository.FindCallback.html │ │ │ ├── ModelRepository.html │ │ │ ├── RestAdapter.html │ │ │ ├── RestRepository.html │ │ │ ├── User.html │ │ │ ├── UserRepository.LoginCallback.html │ │ │ ├── UserRepository.html │ │ │ ├── callbacks │ │ │ │ ├── EmptyResponseParser.html │ │ │ │ ├── JsonArrayParser.html │ │ │ │ ├── JsonObjectParser.html │ │ │ │ ├── ListCallback.html │ │ │ │ ├── ObjectCallback.html │ │ │ │ ├── VoidCallback.html │ │ │ │ ├── package-frame.html │ │ │ │ ├── package-summary.html │ │ │ │ └── package-tree.html │ │ │ ├── package-frame.html │ │ │ ├── package-summary.html │ │ │ └── package-tree.html │ │ │ └── remoting │ │ │ ├── BeanUtil.html │ │ │ ├── JsonUtil.html │ │ │ ├── Repository.html │ │ │ ├── Transient.html │ │ │ ├── VirtualObject.html │ │ │ ├── adapters │ │ │ ├── Adapter.BinaryCallback.html │ │ │ ├── Adapter.Callback.html │ │ │ ├── Adapter.JsonArrayCallback.html │ │ │ ├── Adapter.JsonCallback.html │ │ │ ├── Adapter.JsonObjectCallback.html │ │ │ ├── Adapter.html │ │ │ ├── RestAdapter.html │ │ │ ├── RestContract.html │ │ │ ├── RestContractItem.html │ │ │ ├── StreamParam.html │ │ │ ├── package-frame.html │ │ │ ├── package-summary.html │ │ │ └── package-tree.html │ │ │ ├── package-frame.html │ │ │ ├── package-summary.html │ │ │ └── package-tree.html │ ├── constant-values.html │ ├── deprecated-list.html │ ├── help-doc.html │ ├── index-all.html │ ├── index.html │ ├── overview-frame.html │ ├── overview-summary.html │ ├── overview-tree.html │ ├── package-list │ ├── resources │ │ ├── background.gif │ │ ├── tab.gif │ │ ├── titlebar.gif │ │ └── titlebar_end.gif │ └── stylesheet.css ├── img │ ├── guide-01-cover.png │ ├── guide-02-introduction.png │ ├── guide-03-lesson-one.png │ ├── guide-04-lesson-two.png │ ├── guide-05-lesson-three.png │ └── guide-06-finale.png └── index.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── intl ├── de │ └── messages.json ├── en │ └── messages.json ├── es │ └── messages.json ├── fr │ └── messages.json ├── it │ └── messages.json ├── ja │ └── messages.json ├── ko │ └── messages.json ├── nl │ └── messages.json ├── pt │ └── messages.json ├── tr │ └── messages.json ├── zh-Hans │ └── messages.json └── zh-Hant │ └── messages.json ├── package.json ├── settings.gradle ├── src ├── androidTest │ ├── java │ │ └── com │ │ │ └── strongloop │ │ │ └── android │ │ │ ├── loopback │ │ │ └── test │ │ │ │ ├── AsyncTask.java │ │ │ │ ├── AsyncTestCase.java │ │ │ │ ├── FileTest.java │ │ │ │ ├── LocalInstallationTest.java │ │ │ │ ├── ModelSubclassingTest.java │ │ │ │ ├── ModelTest.java │ │ │ │ ├── RestAdapterTest.java │ │ │ │ ├── TestHelpers.java │ │ │ │ ├── UserTest.java │ │ │ │ └── helpers │ │ │ │ └── TestContext.java │ │ │ └── remoting │ │ │ └── test │ │ │ ├── AsyncTestCase.java │ │ │ ├── BeanUtilTest.java │ │ │ ├── JsonUtilTest.java │ │ │ ├── RestAdapterTest.java │ │ │ └── RestContractTest.java │ └── res │ │ ├── drawable-hdpi │ │ └── ic_launcher.png │ │ ├── drawable-ldpi │ │ └── ic_launcher.png │ │ ├── drawable-mdpi │ │ └── ic_launcher.png │ │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ │ └── values │ │ └── strings.xml └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── strongloop │ └── android │ ├── loopback │ ├── AccessToken.java │ ├── AccessTokenRepository.java │ ├── Container.java │ ├── ContainerRepository.java │ ├── File.java │ ├── FileRepository.java │ ├── LocalInstallation.java │ ├── Model.java │ ├── ModelRepository.java │ ├── RestAdapter.java │ ├── RestRepository.java │ ├── User.java │ ├── UserRepository.java │ └── callbacks │ │ ├── EmptyResponseParser.java │ │ ├── JsonArrayParser.java │ │ ├── JsonObjectParser.java │ │ ├── ListCallback.java │ │ ├── ObjectCallback.java │ │ └── VoidCallback.java │ └── remoting │ ├── BeanUtil.java │ ├── JsonUtil.java │ ├── Repository.java │ ├── Transient.java │ ├── VirtualObject.java │ └── adapters │ ├── Adapter.java │ ├── RestAdapter.java │ ├── RestContract.java │ ├── RestContractItem.java │ └── StreamParam.java └── test-server ├── index.js └── remoting ├── contract-class.js ├── contract.js ├── index.js ├── simple-class.js └── simple.js /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | # Description/Steps to reproduce 11 | 12 | 16 | 17 | # Link to reproduction sandbox 18 | 19 | 24 | 25 | # Expected result 26 | 27 | 30 | 31 | # Additional information 32 | 33 | 38 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | 4 | #### Related issues 5 | 6 | 12 | 13 | - connect to 14 | 15 | ### Checklist 16 | 17 | 22 | 23 | - [ ] New tests added or existing tests modified to cover all changes 24 | - [ ] Code conforms with the [style 25 | guide](http://loopback.io/doc/en/contrib/style-guide.html) 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Eclipse project files 19 | .classpath 20 | .project 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Intellij project files 26 | *.ipr 27 | *.iws 28 | *.iml 29 | .idea 30 | 31 | /build 32 | 33 | .gradle 34 | /local.properties 35 | .DS_Store 36 | 37 | node_modules 38 | /test-server/storage/ 39 | .settings 40 | /target 41 | project.properties 42 | 43 | !intl/ 44 | 45 | 46 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | *.ipr 4 | *.iws 5 | *.iml 6 | /.idea 7 | 8 | .classpath 9 | .project 10 | 11 | /*.properties 12 | /*.settings 13 | /*.gradle 14 | /gradle* 15 | 16 | proguard/ 17 | build/ 18 | 19 | /src/ 20 | /test-server/ 21 | 22 | /*.tgz 23 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners, 3 | # the last matching pattern has the most precendence. 4 | 5 | # Core team members from IBM 6 | * @kjdelisle @jannyHou @loay @b-admike @ssh24 @virkt25 @dhmlau 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Contributing ### 2 | 3 | Thank you for your interest in `loopback-sdk-android`, an open source project 4 | administered by StrongLoop. 5 | 6 | Contributing to `loopback-sdk-android` is easy. In a few simple steps: 7 | 8 | * Ensure that your effort is aligned with the project's roadmap by 9 | talking to the maintainers, especially if you are going to spend a 10 | lot of time on it. 11 | 12 | * Make something better or fix a bug. 13 | 14 | * Adhere to code style outlined in the [Google C++ Style Guide][] and 15 | [Google Javascript Style Guide][]. 16 | 17 | * Sign the [Contributor License Agreement](https://cla.strongloop.com/agreements/strongloop/loopback-sdk-android) 18 | 19 | * Submit a pull request through Github. 20 | 21 | 22 | ### Contributor License Agreement ### 23 | 24 | ``` 25 | Individual Contributor License Agreement 26 | 27 | By signing this Individual Contributor License Agreement 28 | ("Agreement"), and making a Contribution (as defined below) to 29 | StrongLoop, Inc. ("StrongLoop"), You (as defined below) accept and 30 | agree to the following terms and conditions for Your present and 31 | future Contributions submitted to StrongLoop. Except for the license 32 | granted in this Agreement to StrongLoop and recipients of software 33 | distributed by StrongLoop, You reserve all right, title, and interest 34 | in and to Your Contributions. 35 | 36 | 1. Definitions 37 | 38 | "You" or "Your" shall mean the copyright owner or the individual 39 | authorized by the copyright owner that is entering into this 40 | Agreement with StrongLoop. 41 | 42 | "Contribution" shall mean any original work of authorship, 43 | including any modifications or additions to an existing work, that 44 | is intentionally submitted by You to StrongLoop for inclusion in, 45 | or documentation of, any of the products owned or managed by 46 | StrongLoop ("Work"). For purposes of this definition, "submitted" 47 | means any form of electronic, verbal, or written communication 48 | sent to StrongLoop or its representatives, including but not 49 | limited to communication or electronic mailing lists, source code 50 | control systems, and issue tracking systems that are managed by, 51 | or on behalf of, StrongLoop for the purpose of discussing and 52 | improving the Work, but excluding communication that is 53 | conspicuously marked or otherwise designated in writing by You as 54 | "Not a Contribution." 55 | 56 | 2. You Grant a Copyright License to StrongLoop 57 | 58 | Subject to the terms and conditions of this Agreement, You hereby 59 | grant to StrongLoop and recipients of software distributed by 60 | StrongLoop, a perpetual, worldwide, non-exclusive, no-charge, 61 | royalty-free, irrevocable copyright license to reproduce, prepare 62 | derivative works of, publicly display, publicly perform, 63 | sublicense, and distribute Your Contributions and such derivative 64 | works under any license and without any restrictions. 65 | 66 | 3. You Grant a Patent License to StrongLoop 67 | 68 | Subject to the terms and conditions of this Agreement, You hereby 69 | grant to StrongLoop and to recipients of software distributed by 70 | StrongLoop a perpetual, worldwide, non-exclusive, no-charge, 71 | royalty-free, irrevocable (except as stated in this Section) 72 | patent license to make, have made, use, offer to sell, sell, 73 | import, and otherwise transfer the Work under any license and 74 | without any restrictions. The patent license You grant to 75 | StrongLoop under this Section applies only to those patent claims 76 | licensable by You that are necessarily infringed by Your 77 | Contributions(s) alone or by combination of Your Contributions(s) 78 | with the Work to which such Contribution(s) was submitted. If any 79 | entity institutes a patent litigation against You or any other 80 | entity (including a cross-claim or counterclaim in a lawsuit) 81 | alleging that Your Contribution, or the Work to which You have 82 | contributed, constitutes direct or contributory patent 83 | infringement, any patent licenses granted to that entity under 84 | this Agreement for that Contribution or Work shall terminate as 85 | of the date such litigation is filed. 86 | 87 | 4. You Have the Right to Grant Licenses to StrongLoop 88 | 89 | You represent that You are legally entitled to grant the licenses 90 | in this Agreement. 91 | 92 | If Your employer(s) has rights to intellectual property that You 93 | create, You represent that You have received permission to make 94 | the Contributions on behalf of that employer, that Your employer 95 | has waived such rights for Your Contributions, or that Your 96 | employer has executed a separate Corporate Contributor License 97 | Agreement with StrongLoop. 98 | 99 | 5. The Contributions Are Your Original Work 100 | 101 | You represent that each of Your Contributions are Your original 102 | works of authorship (see Section 8 (Submissions on Behalf of 103 | Others) for submission on behalf of others). You represent that to 104 | Your knowledge, no other person claims, or has the right to claim, 105 | any right in any intellectual property right related to Your 106 | Contributions. 107 | 108 | You also represent that You are not legally obligated, whether by 109 | entering into an agreement or otherwise, in any way that conflicts 110 | with the terms of this Agreement. 111 | 112 | You represent that Your Contribution submissions include complete 113 | details of any third-party license or other restriction (including, 114 | but not limited to, related patents and trademarks) of which You 115 | are personally aware and which are associated with any part of 116 | Your Contributions. 117 | 118 | 6. You Don't Have an Obligation to Provide Support for Your Contributions 119 | 120 | You are not expected to provide support for Your Contributions, 121 | except to the extent You desire to provide support. You may provide 122 | support for free, for a fee, or not at all. 123 | 124 | 6. No Warranties or Conditions 125 | 126 | StrongLoop acknowledges that unless required by applicable law or 127 | agreed to in writing, You provide Your Contributions on an "AS IS" 128 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 129 | EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES 130 | OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR 131 | FITNESS FOR A PARTICULAR PURPOSE. 132 | 133 | 7. Submission on Behalf of Others 134 | 135 | If You wish to submit work that is not Your original creation, You 136 | may submit it to StrongLoop separately from any Contribution, 137 | identifying the complete details of its source and of any license 138 | or other restriction (including, but not limited to, related 139 | patents, trademarks, and license agreements) of which You are 140 | personally aware, and conspicuously marking the work as 141 | "Submitted on Behalf of a Third-Party: [named here]". 142 | 143 | 8. Agree to Notify of Change of Circumstances 144 | 145 | You agree to notify StrongLoop of any facts or circumstances of 146 | which You become aware that would make these representations 147 | inaccurate in any respect. Email us at callback@strongloop.com. 148 | ``` 149 | 150 | [Google C++ Style Guide]: https://google.github.io/styleguide/cppguide.html 151 | [Google Javascript Style Guide]: https://google.github.io/styleguide/javascriptguide.xml 152 | -------------------------------------------------------------------------------- /DEVELOPING.md: -------------------------------------------------------------------------------- 1 | ## Set up your development environment 2 | 3 | * Install [Android Studio](http://developer.android.com/sdk/installing/studio.html) 4 | * Open Tools > Android > SDK Manager, make sure you have these modules 5 | installed: 6 | * Tools > Android SDK Platform-tools - version 21 or newer 7 | * Tools > Android SDK Build-tools - version 21.1.2 8 | * Android 5.0.1 (API 21) > SDK Platform 9 | * Extras > Android Support Repository 10 | * Extras > Android Support Library 11 | 12 | * Download test dependencies: 13 | * Run `npm install` in the terminal. 14 | 15 | * Compile and run unit-test: 16 | * Run test server in the terminal: `node test-server` 17 | * In Android Studio, right-click on strong-remoting-android and 18 | select "Run All Tests" 19 | * Add an emulator configuration in AVD manager (you will do this only once) 20 | * Watch the tests pass. 21 | 22 | * Run the Gradle task `dist` to create a ZIP archive containing all JARs 23 | needed for using LoopBack Android SDK in an Eclipse ADT project. The archive 24 | will be created in `build/distributions/`. 25 | 26 | * Run the Gradle task `install` to publish artefacts to your 27 | local Maven cache. This will way you can test the changes in your 28 | application and/or other modules before publishing an official version. 29 | 30 | ## Update the auto-generated API javadoc 31 | 32 | Follow these steps to update the API documentation at http://docs.strongloop.com: 33 | 34 | 1. Update javadoc files 35 | 36 | $ rm -rf build/docs/javadoc/ 37 | $ ./gradlew updateApiDocs -P version={version-to-print-in-docs} \ 38 | -P strongRemotingVersion={version-to-reference} 39 | 40 | 1. Commit your changes to git 41 | 42 | $ git add docs/api 43 | $ git commit 44 | # enter commit message 45 | $ git push 46 | 47 | 1. Submit a GitHub pull request (see [CONTRIBUTING.md](CONTRIBUTING.md)). 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) IBM Corp. 2013,2017. All Rights Reserved. 2 | Node module: loopback-sdk-android 3 | This project is licensed under the MIT License, full text below. 4 | 5 | -------- 6 | 7 | MIT license 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LoopBack Android SDK 2 | 3 | As a former StrongLoop Labs project, this SDK may lack usability, completeness, documentation, and robustness, and may be outdated. StrongLoop/IBM is no longer maintaining this project actively, however we do provide support for our paying customers through usual IBM support channels. 4 | 5 | Our recommendatios is to use [IBM Mobile First](https://www.ibm.com/mobile) platform or [Swagger codegen](https://github.com/swagger-api/swagger-codegen) instead of this SDK. 6 | 7 | We are looking for volunteers from our community to pick up the maintenance of this project, see https://github.com/strongloop-community/loopback-sdk-android/issues/123 8 | ## Description 9 | 10 | The Android Java SDK provides simple API calls that enable your Android app to access a 11 | [LoopBack](http://docs.strongloop.com/loopback) server application. It enables you to interact with your 12 | models and data sources using a comfortable, first-class Java interface instead 13 | of using the clunky `AsyncHttpClient`, `JSONObject`, and similar interfaces. 14 | 15 | See the official [LoopBack Android SDK documentation](http://loopback.io/doc/en/lb3/Android-SDK.html). 16 | 17 | See also [SDK API Reference](http://apidocs.strongloop.com/loopback-sdk-android/api/index.html). 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets": { 3 | "/img": "docs/img", 4 | "/api": "docs/api" 5 | }, 6 | "content": [ 7 | "docs/index.md" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /docs/README.dist: -------------------------------------------------------------------------------- 1 | LoopBack SDK for Android 2 | ======================== 3 | 4 | This is an Eclipse ADT bundle of LoopBack SDK for Android. 5 | 6 | Usage: 7 | 8 | Copy content of the libs/ folder into the libs/ folder of your 9 | Eclipse ADT project. 10 | 11 | Full documentation: 12 | 13 | http://docs.strongloop.com/display/DOC/Android+SDK 14 | -------------------------------------------------------------------------------- /docs/Subclassing.md: -------------------------------------------------------------------------------- 1 | ## Creating Your Own Model: Subclassing 2 | 3 | ### Prerequisites 4 | 5 | - **Knowledge of Java and Android App Development** 6 | - **LoopBack Android SDK** - You should know how to set this up already 7 | if you've gone through the [Getting Started](#getting-started). 8 | If not, run through that guide first. It doesn't take long, and it provides 9 | the basis for this guide. 10 | - **Schema** - Explaining the type of data to store and why is outside the 11 | scope of this guide, being tightly coupled to your application's needs. 12 | 13 | ### Summary 14 | 15 | Creating a subclass of Model allows you to profit from all the benefits of 16 | a Java class (e.g. compile-time type checking) within your LoopBack data 17 | types. 18 | 19 | ### Step 1: Model Class & Properties 20 | 21 | As with any Java class, the first step is to build your interface. If we 22 | leave any [custom behaviour](#http://docs.strongloop.com/strong-remoting) for 23 | later, then it's just a few property declarations and we're ready for the 24 | implementation. 25 | 26 | ```java 27 | import java.math.BigDecimal; 28 | import com.strongloop.android.loopback.Model; 29 | 30 | /** 31 | * A widget for sale. 32 | */ 33 | public class Widget extends Model { // This is a subclass, after all. 34 | 35 | // Being for sale, each widget has a way to be identified and an amount of 36 | // currency to be exchanged for it. Identifying the currency to be exchanged is 37 | // left as an uninteresting exercise for any financial programmers reading this. 38 | 39 | private String name; 40 | private BigDecimal price; 41 | 42 | public void setName(String name) { 43 | this.name = name; 44 | } 45 | 46 | public String getName() { 47 | return name; 48 | } 49 | 50 | public void setPrice(BigDecimal price) { 51 | this.price = price; 52 | } 53 | 54 | public BigDecimal getPrice() { 55 | return price; 56 | } 57 | } 58 | ``` 59 | 60 | ### Step 3: Model Repository 61 | 62 | The `ModelRepository` is the LoopBack Android SDK's 63 | placeholder for what in Node is a JavaScript prototype representing 64 | a specific "type" of Model on the server. In our example, this would be the 65 | model exposed as "widget" (or similar) on the server: 66 | 67 | ```javascript 68 | // server-side javascript 69 | var Widget = loopback.createModel('widget', { 70 | name: String, 71 | price: Number 72 | }); 73 | ``` 74 | 75 | Because of this the className (`'widget'`, above) needs to match the name 76 | that model was given on the server. _If you don't have a model, [see this 77 | guide](#) for more information._ The model _must_ exist (even if the schema is 78 | empty) before it can be interacted with. 79 | 80 | **TL;DR** - Use this to make creating Models easier. Match the name or create 81 | your own. 82 | 83 | Since `ModelRepository` provides a basic implementation, we only need to 84 | override its constructor to provide the appropriate name. 85 | 86 | ```java 87 | public class WidgetRepository extends ModelRepository { 88 | public WidgetRepository() { 89 | super("widget", Widget.class); 90 | } 91 | } 92 | ``` 93 | 94 | ### Step 5: A Little Glue 95 | 96 | Just as we did in [the getting started guide](#getting-started), we'll need an 97 | `RestAdapter` instance to connect to our server: 98 | 99 | ```java 100 | RestAdapter adapter = new RestAdapter("http://myserver:3000"); 101 | ``` 102 | 103 | **Remember:** Replace `"http://myserver:3000"` with the complete URL to your 104 | server. 105 | 106 | Once we have that adapter, we can create our Repository instance. 107 | 108 | ```java 109 | WidgetRepository repository = adapter.createRepository(WidgetRepository.class); 110 | ``` 111 | 112 | ### Step 6: Profit! 113 | 114 | Now that we have a `WidgetRepository` instance, we can: 115 | 116 | - Create a `Widget` 117 | 118 | ```java 119 | Widget pencil = repository.createModel(ImmutableMap.of("name", "Pencil")); 120 | pencil.price = new BigDecimal("1.50"); 121 | ``` 122 | 123 | - Save said `Widget` 124 | 125 | ```java 126 | pencil.save(new Model.Callback() { 127 | @Override 128 | public void onSuccess() { 129 | // Pencil now exists on the server! 130 | } 131 | 132 | @Override 133 | public void onError(Throwable t) { 134 | // save failed, handle the error 135 | } 136 | ``` 137 | 138 | - Find another `Widget` 139 | 140 | ```java 141 | repository.findById(2, new ModelRepository.FindCallback() { 142 | @Override 143 | public void onSuccess(Widget widget) { 144 | // found! 145 | } 146 | 147 | public void onError(Throwable t) { 148 | // handle the error 149 | } 150 | }); 151 | ``` 152 | 153 | - Remove a `Widget` 154 | 155 | ```java 156 | pencil.destroy(new Model.Callback() { 157 | @Override 158 | public void onSuccess() { 159 | // No more pencil. Long live Pen! 160 | } 161 | 162 | @Override 163 | public void onError(Throwable t) { 164 | // handle the error 165 | } 166 | }); 167 | ``` 168 | -------------------------------------------------------------------------------- /docs/api/allclasses-frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | All Classes (loopback-sdk-android 1.5.0 API) 7 | 8 | 9 | 10 | 11 |

All Classes

12 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /docs/api/allclasses-noframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | All Classes (loopback-sdk-android 1.5.0 API) 7 | 8 | 9 | 10 | 11 |

All Classes

12 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /docs/api/com/strongloop/android/loopback/Model.Callback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Model.Callback (loopback-sdk-android 1.5.0 API) 7 | 8 | 9 | 10 | 11 | 17 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 35 |
36 | 78 | 79 | 80 |
81 |
com.strongloop.android.loopback
82 |

Interface Model.Callback

83 |
84 |
85 |
86 |
    87 |
  • 88 |
    89 |
    All Superinterfaces:
    90 |
    VoidCallback
    91 |
    92 |
    93 |
    Enclosing class:
    94 |
    Model
    95 |
    96 |
    97 |
    Deprecated.  98 |
    Use {link VoidCallback} instead.
    99 |
    100 |
    101 |
    public static interface Model.Callback
    102 | extends VoidCallback
    103 |
  • 104 |
105 |
106 |
107 | 126 |
127 |
128 | 129 | 130 |
131 | 132 | 133 | 134 | 135 | 144 |
145 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /docs/api/com/strongloop/android/loopback/callbacks/package-frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | com.strongloop.android.loopback.callbacks (loopback-sdk-android 1.5.0 API) 7 | 8 | 9 | 10 | 11 |

com.strongloop.android.loopback.callbacks

12 |
13 |

Interfaces

14 | 19 |

Classes

20 | 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/api/com/strongloop/android/loopback/package-frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | com.strongloop.android.loopback (loopback-sdk-android 1.5.0 API) 7 | 8 | 9 | 10 | 11 |

com.strongloop.android.loopback

12 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/api/com/strongloop/android/remoting/Transient.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Transient (loopback-sdk-android 1.5.0 API) 7 | 8 | 9 | 10 | 11 | 17 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 35 |
36 | 74 | 75 | 76 |
77 |
com.strongloop.android.remoting
78 |

Annotation Type Transient

79 |
80 |
81 |
82 |
    83 |
  • 84 |
    85 |
    86 |
    @Target(value=METHOD)
     87 | @Retention(value=RUNTIME)
     88 | public @interface Transient
    89 |
    An annotation marking a class member as transient, 90 | i.e. excluded from toMap/JSON serialization.
    91 |
  • 92 |
93 |
94 |
95 | 96 | 97 |
98 | 99 | 100 | 101 | 102 | 111 |
112 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /docs/api/com/strongloop/android/remoting/adapters/package-frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | com.strongloop.android.remoting.adapters (loopback-sdk-android 1.5.0 API) 7 | 8 | 9 | 10 | 11 |

com.strongloop.android.remoting.adapters

12 |
13 |

Interfaces

14 | 18 |

Classes

19 | 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /docs/api/com/strongloop/android/remoting/package-frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | com.strongloop.android.remoting (loopback-sdk-android 1.5.0 API) 7 | 8 | 9 | 10 | 11 |

com.strongloop.android.remoting

12 |
13 |

Classes

14 | 20 |

Annotation Types

21 | 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /docs/api/com/strongloop/android/remoting/package-summary.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | com.strongloop.android.remoting (loopback-sdk-android 1.5.0 API) 7 | 8 | 9 | 10 | 11 | 17 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 35 |
36 | 63 | 64 |
65 |

Package com.strongloop.android.remoting

66 |
67 |
68 |
    69 |
  • 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 87 | 88 | 89 | 90 | 94 | 95 | 96 | 97 | 100 | 101 | 102 |
    Class Summary 
    ClassDescription
    BeanUtil 
    JsonUtil 84 |
    Utility methods for converting JSON objects to Java collection objects 85 | (and vice versa).
    86 |
    Repository<T extends VirtualObject> 91 |
    A local representative of remote model repository, it provides 92 | access to static methods like
    93 |
    VirtualObject 98 |
    A local representative of a single virtual object.
    99 |
    103 |
  • 104 |
  • 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 118 | 119 | 120 |
    Annotation Types Summary 
    Annotation TypeDescription
    Transient 115 |
    An annotation marking a class member as transient, 116 | i.e.
    117 |
    121 |
  • 122 |
123 |
124 | 125 |
126 | 127 | 128 | 129 | 130 | 139 |
140 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /docs/api/com/strongloop/android/remoting/package-tree.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | com.strongloop.android.remoting Class Hierarchy (loopback-sdk-android 1.5.0 API) 7 | 8 | 9 | 10 | 11 | 17 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 35 |
36 | 63 | 64 |
65 |

Hierarchy For Package com.strongloop.android.remoting

66 | Package Hierarchies: 67 | 70 |
71 |
72 |

Class Hierarchy

73 |
    74 |
  • java.lang.Object 75 |
      76 |
    • com.strongloop.android.remoting.BeanUtil
    • 77 |
    • com.strongloop.android.remoting.JsonUtil
    • 78 |
    • com.strongloop.android.remoting.Repository<T>
    • 79 |
    • com.strongloop.android.remoting.VirtualObject
    • 80 |
    81 |
  • 82 |
83 |

Annotation Type Hierarchy

84 |
    85 |
  • com.strongloop.android.remoting.Transient (implements java.lang.annotation.Annotation)
  • 86 |
87 |
88 | 89 |
90 | 91 | 92 | 93 | 94 | 103 |
104 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /docs/api/deprecated-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Deprecated List (loopback-sdk-android 1.5.0 API) 7 | 8 | 9 | 10 | 11 | 17 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 35 |
36 | 63 | 64 |
65 |

Deprecated API

66 |

Contents

67 | 72 |
73 |
74 | 75 | 76 | 103 | 104 | 105 | 106 | 123 | 124 | 125 | 126 | 143 |
144 | 145 |
146 | 147 | 148 | 149 | 150 | 159 |
160 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /docs/api/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | loopback-sdk-android 1.5.0 API 7 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | <noscript> 68 | <div>JavaScript is disabled on your browser.</div> 69 | </noscript> 70 | <h2>Frame Alert</h2> 71 | <p>This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client. Link to <a href="overview-summary.html">Non-frame version</a>.</p> 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /docs/api/overview-frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Overview List (loopback-sdk-android 1.5.0 API) 7 | 8 | 9 | 10 | 11 | 12 | 21 |

 

22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/api/overview-summary.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Overview (loopback-sdk-android 1.5.0 API) 7 | 8 | 9 | 10 | 11 | 17 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 35 |
36 | 63 | 64 |
65 |

loopback-sdk-android 1.5.0 API

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 |
Packages 
PackageDescription
com.strongloop.android.loopback 
com.strongloop.android.loopback.callbacks 
com.strongloop.android.remoting 
com.strongloop.android.remoting.adapters 
93 |
94 | 95 |
96 | 97 | 98 | 99 | 100 | 109 |
110 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /docs/api/package-list: -------------------------------------------------------------------------------- 1 | com.strongloop.android.loopback 2 | com.strongloop.android.loopback.callbacks 3 | com.strongloop.android.remoting 4 | com.strongloop.android.remoting.adapters 5 | -------------------------------------------------------------------------------- /docs/api/resources/background.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strongloop-community/loopback-sdk-android/9e1e304a66b6bd0557623dc81a22a28d9fe7a62d/docs/api/resources/background.gif -------------------------------------------------------------------------------- /docs/api/resources/tab.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strongloop-community/loopback-sdk-android/9e1e304a66b6bd0557623dc81a22a28d9fe7a62d/docs/api/resources/tab.gif -------------------------------------------------------------------------------- /docs/api/resources/titlebar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strongloop-community/loopback-sdk-android/9e1e304a66b6bd0557623dc81a22a28d9fe7a62d/docs/api/resources/titlebar.gif -------------------------------------------------------------------------------- /docs/api/resources/titlebar_end.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strongloop-community/loopback-sdk-android/9e1e304a66b6bd0557623dc81a22a28d9fe7a62d/docs/api/resources/titlebar_end.gif -------------------------------------------------------------------------------- /docs/img/guide-01-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strongloop-community/loopback-sdk-android/9e1e304a66b6bd0557623dc81a22a28d9fe7a62d/docs/img/guide-01-cover.png -------------------------------------------------------------------------------- /docs/img/guide-02-introduction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strongloop-community/loopback-sdk-android/9e1e304a66b6bd0557623dc81a22a28d9fe7a62d/docs/img/guide-02-introduction.png -------------------------------------------------------------------------------- /docs/img/guide-03-lesson-one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strongloop-community/loopback-sdk-android/9e1e304a66b6bd0557623dc81a22a28d9fe7a62d/docs/img/guide-03-lesson-one.png -------------------------------------------------------------------------------- /docs/img/guide-04-lesson-two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strongloop-community/loopback-sdk-android/9e1e304a66b6bd0557623dc81a22a28d9fe7a62d/docs/img/guide-04-lesson-two.png -------------------------------------------------------------------------------- /docs/img/guide-05-lesson-three.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strongloop-community/loopback-sdk-android/9e1e304a66b6bd0557623dc81a22a28d9fe7a62d/docs/img/guide-05-lesson-three.png -------------------------------------------------------------------------------- /docs/img/guide-06-finale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strongloop-community/loopback-sdk-android/9e1e304a66b6bd0557623dc81a22a28d9fe7a62d/docs/img/guide-06-finale.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | Please see the LoopBack 2 | [Android SDK API Reference](api/index.html). 3 | 4 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=1.6.1-SNAPSHOT 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strongloop-community/loopback-sdk-android/9e1e304a66b6bd0557623dc81a22a28d9fe7a62d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Mar 31 13:40:28 CEST 2015 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 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /intl/de/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "b3c4583582900f8c4f771d1155d7d610": "{{LoopBack}}-Testserver empfangsbereit unter {{http://localhost:3000/}}", 3 | "a8b778a47c78fe90b8891b4e73670036": "{{strong-remoting}}-Testserver empfangsbereit unter {{http://localhost:3001/}}" 4 | } 5 | 6 | -------------------------------------------------------------------------------- /intl/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "a8b778a47c78fe90b8891b4e73670036": "{{strong-remoting}} test server listening on {{http://localhost:3001/}}", 3 | "b3c4583582900f8c4f771d1155d7d610": "{{LoopBack}} test server listening on {{http://localhost:3000/}}" 4 | } 5 | -------------------------------------------------------------------------------- /intl/es/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "b3c4583582900f8c4f771d1155d7d610": "Servidor de pruebas de {{LoopBack}} a la escucha en {{http://localhost:3000/}}", 3 | "a8b778a47c78fe90b8891b4e73670036": "Servidor de pruebas de {{strong-remoting}} a la escucha en {{http://localhost:3001/}}" 4 | } 5 | 6 | -------------------------------------------------------------------------------- /intl/fr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "b3c4583582900f8c4f771d1155d7d610": "le serveur de test {{LoopBack}} est à l'écoute sur {{http://localhost:3000/}}", 3 | "a8b778a47c78fe90b8891b4e73670036": "le serveur de test {{strong-remoting}} est à l'écoute sur {{http://localhost:3001/}}" 4 | } 5 | 6 | -------------------------------------------------------------------------------- /intl/it/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "b3c4583582900f8c4f771d1155d7d610": "Server di test {{LoopBack}} in ascolto su {{http://localhost:3000/}}", 3 | "a8b778a47c78fe90b8891b4e73670036": "Server di test {{strong-remoting}} in ascolto su {{http://localhost:3001/}}" 4 | } 5 | 6 | -------------------------------------------------------------------------------- /intl/ja/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "b3c4583582900f8c4f771d1155d7d610": "{{http://localhost:3000/}} で listen する {{LoopBack}} テスト・サーバー", 3 | "a8b778a47c78fe90b8891b4e73670036": "{{http://localhost:3001/}} で listen する {{strong-remoting}} テスト・サーバー" 4 | } 5 | 6 | -------------------------------------------------------------------------------- /intl/ko/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "b3c4583582900f8c4f771d1155d7d610": "{{http://localhost:3000/}}에서 청취 중인 {{LoopBack}} 테스트 서버", 3 | "a8b778a47c78fe90b8891b4e73670036": "{{http://localhost:3001/}}에서 청취 중인 {{strong-remoting}} 테스트 서버" 4 | } 5 | 6 | -------------------------------------------------------------------------------- /intl/nl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "b3c4583582900f8c4f771d1155d7d610": "{{LoopBack}}-testserver luistert naar {{http://localhost:3000/}}", 3 | "a8b778a47c78fe90b8891b4e73670036": "{{strong-remoting}}-testserver luistert naar {{http://localhost:3001/}}" 4 | } 5 | 6 | -------------------------------------------------------------------------------- /intl/pt/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "b3c4583582900f8c4f771d1155d7d610": "Servidor de teste de {{LoopBack}} atendendo em {{http://localhost:3000/}}", 3 | "a8b778a47c78fe90b8891b4e73670036": "Servidor de teste de {{strong-remoting}} atendendo em {{http://localhost:3001/}}" 4 | } 5 | 6 | -------------------------------------------------------------------------------- /intl/tr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "b3c4583582900f8c4f771d1155d7d610": "{{LoopBack}} test sunucusu {{http://localhost:3000/}} üzerinde dinlemede", 3 | "a8b778a47c78fe90b8891b4e73670036": "{{strong-remoting}} test sunucusu {{http://localhost:3001/}} üzerinde dinlemede" 4 | } 5 | 6 | -------------------------------------------------------------------------------- /intl/zh-Hans/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "b3c4583582900f8c4f771d1155d7d610": "{{LoopBack}} 测试服务器正在侦听 {{http://localhost:3000/}}", 3 | "a8b778a47c78fe90b8891b4e73670036": "{{strong-remoting}} 测试服务器正在侦听 {{http://localhost:3001/}}" 4 | } 5 | 6 | -------------------------------------------------------------------------------- /intl/zh-Hant/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "b3c4583582900f8c4f771d1155d7d610": "{{LoopBack}} 測試伺服器正在 {{http://localhost:3000/}} 上接聽", 3 | "a8b778a47c78fe90b8891b4e73670036": "{{strong-remoting}} 測試伺服器正在 {{http://localhost:3001/}} 上接聽" 4 | } 5 | 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "loopback-sdk-android", 3 | "version": "1.6.1", 4 | "description": "Android client SDK for the LoopBack framework.", 5 | "keywords": [ 6 | "StrongLoop Labs", 7 | "LoopBack", 8 | "client", 9 | "SDK", 10 | "iOS", 11 | "mobile" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/strongloop/loopback-sdk-android.git" 16 | }, 17 | "license": "MIT", 18 | "devDependencies": { 19 | "async": "~0.9.0", 20 | "express": "^4.9.8", 21 | "loopback": "^3.0.0", 22 | "loopback-component-push": "1.x", 23 | "loopback-component-storage": "1.x", 24 | "morgan": "^1.4.0", 25 | "strong-remoting": "^2.5.0" 26 | }, 27 | "dependencies": { 28 | "strong-globalize": "^2.5.8" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | // workaround for https://android-review.googlesource.com/#/c/108422/ 2 | include ':' 3 | 4 | rootProject.name = 'loopback-sdk-android' 5 | -------------------------------------------------------------------------------- /src/androidTest/java/com/strongloop/android/loopback/test/AsyncTask.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback.test; 2 | 3 | import com.strongloop.android.loopback.callbacks.ListCallback; 4 | import com.strongloop.android.loopback.callbacks.ObjectCallback; 5 | import com.strongloop.android.loopback.callbacks.VoidCallback; 6 | import com.strongloop.android.remoting.VirtualObject; 7 | 8 | import java.util.concurrent.CountDownLatch; 9 | import java.util.concurrent.TimeUnit; 10 | import java.util.concurrent.TimeoutException; 11 | 12 | public abstract class AsyncTask implements Runnable { 13 | private CountDownLatch signal; 14 | private Throwable failException; 15 | 16 | private Throwable getFailException() { 17 | return failException; 18 | } 19 | 20 | private void setSignal(final CountDownLatch signal) { 21 | this.signal = signal; 22 | } 23 | 24 | protected void notifyFailed(Throwable reason) { 25 | failException = reason; 26 | notifyFinished(); 27 | } 28 | 29 | protected void notifyFinished() { 30 | signal.countDown(); 31 | } 32 | 33 | /** 34 | * VoidCallback that reports error as test failures. 35 | */ 36 | public class VoidTestCallback implements VoidCallback { 37 | @Override 38 | public void onSuccess() { 39 | notifyFinished(); 40 | } 41 | 42 | @Override 43 | public void onError(Throwable t) { 44 | notifyFailed(t); 45 | } 46 | } 47 | 48 | /** 49 | * ObjectCallback that reports errors as test failures. 50 | * @param The Model type. 51 | */ 52 | public abstract class ObjectTestCallback 53 | implements ObjectCallback { 54 | 55 | @Override 56 | public void onError(Throwable t) { 57 | notifyFailed(t); 58 | } 59 | } 60 | 61 | /** 62 | * ListCallback that reports errors as test failures. 63 | * @param The Model type. 64 | */ 65 | public abstract class ListTestCallback 66 | implements ListCallback { 67 | 68 | @Override 69 | public void onError(Throwable t) { 70 | notifyFailed(t); 71 | } 72 | } 73 | 74 | 75 | public static class Runner implements Runnable { 76 | 77 | private final AsyncTask asyncTask; 78 | private final CountDownLatch signal = new CountDownLatch(1); 79 | private Throwable uncaughtException; 80 | 81 | public Runner(AsyncTask asyncTask) { 82 | this.asyncTask = asyncTask; 83 | this.asyncTask.setSignal(signal); 84 | } 85 | 86 | @Override 87 | public void run() { 88 | try { 89 | asyncTask.run(); 90 | } 91 | catch (Throwable t) { 92 | uncaughtException = t; 93 | signal.countDown(); 94 | } 95 | } 96 | 97 | public void await() throws Throwable { 98 | if (!signal.await(30, TimeUnit.SECONDS)) 99 | uncaughtException = new TimeoutException("The task timed out."); 100 | 101 | if (uncaughtException == null) 102 | uncaughtException = asyncTask.getFailException(); 103 | 104 | if (uncaughtException != null) 105 | throw uncaughtException; 106 | 107 | if (signal.getCount() != 0) 108 | throw new AssertionError("Unexpected result: the task has neither finished nor failed."); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/androidTest/java/com/strongloop/android/loopback/test/AsyncTestCase.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback.test; 2 | 3 | import android.test.ActivityTestCase; 4 | 5 | import com.strongloop.android.loopback.Container; 6 | import com.strongloop.android.loopback.ContainerRepository; 7 | import com.strongloop.android.loopback.File; 8 | import com.strongloop.android.loopback.Model; 9 | import com.strongloop.android.loopback.ModelRepository; 10 | import com.strongloop.android.loopback.RestAdapter; 11 | import com.strongloop.android.loopback.test.helpers.TestContext; 12 | import com.strongloop.android.remoting.adapters.Adapter; 13 | import com.strongloop.android.remoting.adapters.Adapter.JsonObjectCallback; 14 | 15 | import org.json.JSONObject; 16 | 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | /** 21 | * Convenience class to easily perform asynchronous JUnit tests in Android. 22 | */ 23 | public class AsyncTestCase extends ActivityTestCase { 24 | 25 | // NOTE: "10.0.2.2" is the "localhost" of the Android emulator's 26 | // host computer. 27 | public static final String REST_SERVER_URL = "http://10.0.2.2:3000"; 28 | 29 | public TestContext testContext; 30 | 31 | @Override 32 | protected void setUp() throws Exception { 33 | super.setUp(); 34 | testContext = new TestContext(getInstrumentation()); 35 | } 36 | 37 | protected RestAdapter createRestAdapter() { 38 | return new RestAdapter(testContext, REST_SERVER_URL); 39 | } 40 | 41 | public abstract class AsyncTest extends AsyncTask { 42 | 43 | public JsonObjectCallback expectJsonResponse(String expectedData) { 44 | return new ExpectedDataCallback(expectedData); 45 | } 46 | 47 | /** 48 | * Simple JsonObjectCallback to check for the "data" field of 49 | * the response. 50 | */ 51 | public class ExpectedDataCallback extends JsonObjectCallback { 52 | 53 | private String expectedData; 54 | 55 | public ExpectedDataCallback(String expectedData) { 56 | this.expectedData = expectedData; 57 | } 58 | 59 | @Override 60 | public void onError(Throwable t) { 61 | notifyFailed(t); 62 | } 63 | 64 | @Override 65 | public void onSuccess(JSONObject response) { 66 | assertNotNull("No value returned", response); 67 | assertEquals("Incorrect value returned.", expectedData, 68 | response.optString("data")); 69 | notifyFinished(); 70 | } 71 | }; 72 | } 73 | 74 | public void doAsyncTest(final AsyncTest asyncTest) throws Throwable { 75 | await(asyncTest); 76 | } 77 | 78 | public void await(final AsyncTask asyncTask) throws Throwable { 79 | AsyncTask.Runner runner = new AsyncTask.Runner(asyncTask); 80 | runTestOnUiThread(runner); 81 | runner.await(); 82 | } 83 | 84 | public JSONObject fetchJsonObjectById(final ModelRepository repository, final Object id) 85 | throws Throwable { 86 | final JSONObject[] remoteObject = new JSONObject[1]; 87 | 88 | doAsyncTest(new AsyncTest() { 89 | @Override 90 | public void run() { 91 | Map params = new HashMap(); 92 | params.put("id", id); 93 | repository.invokeStaticMethod("findById", params, new Adapter.JsonObjectCallback() { 94 | @Override 95 | public void onError(Throwable t) { 96 | notifyFailed(t); 97 | } 98 | 99 | @Override 100 | public void onSuccess(JSONObject response) { 101 | remoteObject[0] = response; 102 | notifyFinished(); 103 | } 104 | }); 105 | } 106 | }); 107 | return remoteObject[0]; 108 | } 109 | 110 | @SuppressWarnings("unchecked") 111 | public T fetchModelById( 112 | final ModelRepository repository, final Object id) 113 | throws Throwable { 114 | 115 | final Model[] remoteObject = new Model[1]; 116 | doAsyncTest(new AsyncTest() { 117 | @Override 118 | public void run() { 119 | repository.findById(id, new ObjectTestCallback() { 120 | @Override 121 | public void onSuccess(T model) { 122 | remoteObject[0] = model; 123 | notifyFinished(); 124 | } 125 | }); 126 | } 127 | }); 128 | return (T) remoteObject[0]; 129 | } 130 | 131 | public Container givenContainer(final ContainerRepository repository) throws Throwable { 132 | final Container[] ref = new Container[1]; 133 | await(new AsyncTask() { 134 | @Override 135 | public void run() { 136 | repository.create("a-container", new ObjectTestCallback() { 137 | @Override 138 | public void onSuccess(Container object) { 139 | ref[0] = object; 140 | notifyFinished(); 141 | } 142 | }); 143 | } 144 | }); 145 | return ref[0]; 146 | } 147 | 148 | public File givenFile(final ContainerRepository repository, byte[] content) 149 | throws Throwable { 150 | return givenFile(repository, "a-file", content); 151 | } 152 | 153 | public File givenFile(final ContainerRepository repository, final String name) 154 | throws Throwable { 155 | return givenFile(repository, name, new byte[0]); 156 | } 157 | 158 | public File givenFile(final ContainerRepository repository, final String name, final byte[] content) 159 | throws Throwable { 160 | final File[] ref = new File[1]; 161 | final Container container = givenContainer(repository); 162 | 163 | await(new AsyncTask() { 164 | @Override 165 | public void run() { 166 | container.upload(name, content, null, 167 | new ObjectTestCallback() { 168 | @Override 169 | public void onSuccess(File object) { 170 | ref[0] = object; 171 | notifyFinished(); 172 | } 173 | }); 174 | } 175 | }); 176 | return ref[0]; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/androidTest/java/com/strongloop/android/loopback/test/ModelSubclassingTest.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback.test; 2 | 3 | import android.util.Log; 4 | 5 | import com.strongloop.android.loopback.Model; 6 | import com.strongloop.android.loopback.RestAdapter; 7 | import com.strongloop.android.loopback.ModelRepository; 8 | import com.strongloop.android.loopback.callbacks.ObjectCallback; 9 | import com.strongloop.android.loopback.callbacks.VoidCallback; 10 | 11 | import org.json.JSONObject; 12 | 13 | import java.util.HashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | import static com.strongloop.android.loopback.test.TestHelpers.assertPropertyNames; 18 | 19 | public class ModelSubclassingTest extends AsyncTestCase { 20 | 21 | public static class Widget extends Model { 22 | 23 | private String name; 24 | private int bars; 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | public void setName(String name) { 31 | this.name = name; 32 | } 33 | 34 | public int getBars() { 35 | return bars; 36 | } 37 | 38 | public void setBars(int bars) { 39 | this.bars = bars; 40 | } 41 | 42 | } 43 | 44 | public static class WidgetRepository extends ModelRepository { 45 | 46 | public WidgetRepository() { 47 | super("widget", Widget.class); 48 | } 49 | } 50 | 51 | private WidgetRepository widgetRepository; 52 | 53 | @Override 54 | protected void setUp() throws Exception { 55 | super.setUp(); 56 | RestAdapter adapter = createRestAdapter(); 57 | widgetRepository = adapter.createRepository(WidgetRepository.class); 58 | } 59 | 60 | public void testCreateAndRemove() throws Throwable { 61 | final Object[] lastId = new Object[1]; 62 | 63 | Map params = new HashMap(); 64 | params.put("name", "Foobar"); 65 | params.put("bars", 1); 66 | 67 | final Widget model = widgetRepository.createModel(params); 68 | 69 | assertEquals("Foobar", model.getName()); 70 | assertEquals(1, model.getBars()); 71 | assertNull(model.getId()); 72 | 73 | doAsyncTest(new AsyncTest() { 74 | 75 | @Override 76 | public void run() { 77 | model.save(new VoidTestCallback() { 78 | 79 | @Override 80 | public void onSuccess() { 81 | lastId[0] = model.getId(); 82 | Log.i("ModelSubclassingTest", "id: " + model.getId()); 83 | assertNotNull(model.getId()); 84 | notifyFinished(); 85 | } 86 | }); 87 | } 88 | }); 89 | assertNotNull(lastId[0]); 90 | 91 | JSONObject remoteJson = fetchJsonObjectById(widgetRepository, lastId[0]); 92 | assertNotNull(remoteJson); 93 | assertPropertyNames(remoteJson, "id", "name", "bars"); 94 | 95 | doAsyncTest(new AsyncTest() { 96 | 97 | @Override 98 | public void run() { 99 | widgetRepository.findById(lastId[0], 100 | new ObjectTestCallback() { 101 | 102 | @Override 103 | public void onSuccess(Widget model) { 104 | model.destroy(new VoidTestCallback()); 105 | } 106 | }); 107 | } 108 | }); 109 | } 110 | 111 | public void testFind() throws Throwable { 112 | doAsyncTest(new AsyncTest() { 113 | 114 | @Override 115 | public void run() { 116 | widgetRepository.findById(2, new ObjectTestCallback() { 117 | 118 | @Override 119 | public void onSuccess(Widget model) { 120 | assertNotNull("No model found with id 2", model); 121 | assertTrue("Invalid class", (model instanceof Model)); 122 | assertEquals("Invalid name", "Bar", model.getName()); 123 | assertEquals("Invalid bars", 1, model.getBars()); 124 | notifyFinished(); 125 | } 126 | }); 127 | } 128 | }); 129 | } 130 | 131 | public void testFindAll() throws Throwable { 132 | doAsyncTest(new AsyncTest() { 133 | 134 | @Override 135 | public void run() { 136 | widgetRepository.findAll(new ListTestCallback() { 137 | 138 | @Override 139 | public void onSuccess(List list) { 140 | assertNotNull("No models returned.", list); 141 | assertTrue("Invalid # of models returned: " + 142 | list.size(), list.size() >= 2); 143 | assertTrue("Invalid class", 144 | (list.get(0) instanceof Widget)); 145 | assertEquals("Invalid name", "Foo", 146 | list.get(0).getName()); 147 | assertEquals("Invalid bars", 0, 148 | list.get(0).getBars()); 149 | assertEquals("Invalid name", "Bar", 150 | list.get(1).getName()); 151 | assertEquals("Invalid bars", 1, 152 | list.get(1).getBars()); 153 | notifyFinished(); 154 | } 155 | }); 156 | } 157 | }); 158 | } 159 | 160 | public void testUpdate() throws Throwable { 161 | doAsyncTest(new AsyncTest() { 162 | 163 | ObjectCallback verify = 164 | new ObjectTestCallback() { 165 | 166 | @Override 167 | public void onSuccess(Widget model) { 168 | assertNotNull("No model found with id 2", model); 169 | assertTrue("Invalid class", (model instanceof Widget)); 170 | assertEquals("Invalid name", "Barfoo", model.getName()); 171 | assertEquals("Invalid bars", 1, model.getBars()); 172 | 173 | model.setName("Bar"); 174 | model.save(new VoidTestCallback()); 175 | } 176 | }; 177 | 178 | VoidCallback findAgain = new VoidTestCallback() { 179 | 180 | @Override 181 | public void onSuccess() { 182 | widgetRepository.findById(2, verify); 183 | } 184 | }; 185 | 186 | ObjectCallback update = 187 | new ObjectTestCallback() { 188 | 189 | @Override 190 | public void onSuccess(Widget model) { 191 | assertNotNull("No model found with ID 2", model); 192 | model.setName("Barfoo"); 193 | model.save(findAgain); 194 | } 195 | }; 196 | 197 | @Override 198 | public void run() { 199 | widgetRepository.findById(2, update); 200 | } 201 | }); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/androidTest/java/com/strongloop/android/loopback/test/RestAdapterTest.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback.test; 2 | 3 | import android.content.Context; 4 | 5 | import com.strongloop.android.loopback.RestAdapter; 6 | 7 | public class RestAdapterTest extends AsyncTestCase { 8 | private RestAdapter adapter; 9 | 10 | @Override 11 | protected void setUp() throws Exception { 12 | super.setUp(); 13 | testContext.clearSharedPreferences( 14 | RestAdapter.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); 15 | adapter = createRestAdapter(); 16 | } 17 | 18 | public void testAccessTokenIsStoredInSharedPreferences() { 19 | final String[] accessTokenRef = new String[1]; 20 | adapter.setAccessToken("an-access-token"); 21 | 22 | // android-async-http client does not allow inspection of request headers 23 | // the workaround is to override the setter method 24 | new RestAdapter(testContext, REST_SERVER_URL) { 25 | @Override 26 | public void setAccessToken(String value) { 27 | accessTokenRef[0] = value; 28 | } 29 | }; 30 | 31 | assertEquals("an-access-token", accessTokenRef[0]); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/androidTest/java/com/strongloop/android/loopback/test/TestHelpers.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback.test; 2 | 3 | import android.util.Log; 4 | 5 | import com.google.common.collect.ImmutableSet; 6 | import com.strongloop.android.loopback.Container; 7 | import com.strongloop.android.loopback.ContainerRepository; 8 | import com.strongloop.android.loopback.RestAdapter; 9 | 10 | import org.json.JSONObject; 11 | 12 | import java.util.Date; 13 | import java.util.HashSet; 14 | import java.util.Iterator; 15 | import java.util.Set; 16 | 17 | import static junit.framework.Assert.assertEquals; 18 | 19 | public class TestHelpers { 20 | public static void assertPropertyNames(JSONObject obj, String... names) { 21 | Set actual = new HashSet(); 22 | Iterator iter = obj.keys(); 23 | while (iter.hasNext()) 24 | actual.add((String) iter.next()); 25 | 26 | Set expected = ImmutableSet.copyOf(names); 27 | 28 | assertEquals(expected, actual); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/androidTest/java/com/strongloop/android/loopback/test/helpers/TestContext.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback.test.helpers; 2 | 3 | import android.app.Instrumentation; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.content.pm.PackageInfo; 7 | import android.content.pm.PackageManager; 8 | import android.test.mock.MockContext; 9 | import android.test.mock.MockPackageManager; 10 | 11 | public class TestContext extends MockContext { 12 | private final Instrumentation instrumentation; 13 | 14 | private String packageVersionName = "a-version-name"; 15 | private int packageVersionCode = 1; 16 | 17 | public TestContext(Instrumentation instrumentation) { 18 | this.instrumentation = instrumentation; 19 | } 20 | 21 | public void setPackageVersionName(String value) { 22 | packageVersionName = value; 23 | } 24 | 25 | public void setPackageVersionCode(int value) { 26 | packageVersionCode = value; 27 | } 28 | 29 | @Override 30 | public String getPackageName() { 31 | return "a-package-name"; 32 | } 33 | 34 | @Override 35 | public PackageManager getPackageManager() { 36 | return new TestPackageManager(); 37 | } 38 | 39 | @Override 40 | public SharedPreferences getSharedPreferences(String name, int mode) { 41 | return getRealContext().getSharedPreferences(name, mode); 42 | } 43 | 44 | public void clearSharedPreferences(String name, int mode) { 45 | getRealContext() 46 | .getSharedPreferences(name, mode) 47 | .edit().clear().commit(); 48 | } 49 | 50 | private Context getRealContext() { 51 | return instrumentation.getContext(); 52 | } 53 | 54 | class TestPackageManager extends MockPackageManager { 55 | @Override 56 | public PackageInfo getPackageInfo(String packageName, int flags) 57 | throws NameNotFoundException { 58 | 59 | if (packageName != "a-package-name") 60 | throw new NameNotFoundException(); 61 | 62 | PackageInfo info = new PackageInfo(); 63 | info.versionName = packageVersionName; 64 | info.versionCode = packageVersionCode; 65 | return info; 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/androidTest/java/com/strongloop/android/remoting/test/AsyncTestCase.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.remoting.test; 2 | 3 | import android.content.Context; 4 | import android.test.ActivityTestCase; 5 | 6 | import com.strongloop.android.remoting.adapters.Adapter.JsonObjectCallback; 7 | import com.strongloop.android.remoting.adapters.RestAdapter; 8 | 9 | import org.json.JSONObject; 10 | 11 | import java.util.concurrent.CountDownLatch; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | /** 15 | * Convenience class to easily perform asynchronous JUnit tests in Android. 16 | */ 17 | public class AsyncTestCase extends ActivityTestCase { 18 | 19 | // NOTE: "10.0.2.2" is the "localhost" of the Android emulator's host computer. 20 | public static final String REST_SERVER_URL = "http://10.0.2.2:3001"; 21 | 22 | public Context testContext; 23 | 24 | @Override 25 | protected void setUp() throws Exception { 26 | super.setUp(); 27 | testContext = getActivity(); 28 | } 29 | 30 | protected RestAdapter createRestAdapter() { 31 | return new RestAdapter(testContext, REST_SERVER_URL); 32 | } 33 | 34 | public abstract class AsyncTest implements Runnable { 35 | 36 | private CountDownLatch signal; 37 | private Throwable failException; 38 | 39 | public Throwable getFailException() { 40 | return failException; 41 | } 42 | 43 | public void notifyFailed(Throwable reason) { 44 | failException = reason; 45 | notifyFinished(); 46 | } 47 | 48 | public void notifyFinished() { 49 | signal.countDown(); 50 | } 51 | 52 | private void setSignal(final CountDownLatch signal) { 53 | this.signal = signal; 54 | } 55 | 56 | public JsonObjectCallback expectJsonResponse(String expectedData) { 57 | return new ExpectedDataCallback(expectedData); 58 | } 59 | 60 | /** 61 | * Simple JsonObjectCallback to check for the "data" field of 62 | * the response. 63 | */ 64 | public class ExpectedDataCallback extends JsonObjectCallback { 65 | 66 | private String expectedData; 67 | 68 | public ExpectedDataCallback(String expectedData) { 69 | this.expectedData = expectedData; 70 | } 71 | 72 | @Override 73 | public void onError(Throwable t) { 74 | notifyFailed(t); 75 | } 76 | 77 | @Override 78 | public void onSuccess(JSONObject response) { 79 | assertNotNull("No value returned", response); 80 | assertEquals("Incorrect value returned.", expectedData, 81 | response.optString("data")); 82 | notifyFinished(); 83 | } 84 | }; 85 | } 86 | 87 | public void doAsyncTest(final AsyncTest asyncTest) throws Throwable { 88 | TestRunner runner = new TestRunner(asyncTest); 89 | runTestOnUiThread(runner); 90 | 91 | boolean success = runner.await(); 92 | if (runner.getUncaughtException() != null) { 93 | throw runner.getUncaughtException(); 94 | } 95 | if (asyncTest.getFailException() != null) { 96 | throw asyncTest.getFailException(); 97 | } 98 | assertTrue("Test should have finished in 30 seconds.", success); 99 | } 100 | 101 | private static class TestRunner implements Runnable { 102 | 103 | private final AsyncTest asyncTest; 104 | private final CountDownLatch signal = new CountDownLatch(1); 105 | private Throwable uncaughtException; 106 | 107 | public TestRunner(AsyncTest asyncTest) { 108 | this.asyncTest = asyncTest; 109 | this.asyncTest.setSignal(signal); 110 | } 111 | 112 | @Override 113 | public void run() { 114 | try { 115 | asyncTest.run(); 116 | } 117 | catch (Throwable t) { 118 | uncaughtException = t; 119 | } 120 | } 121 | 122 | public boolean await() { 123 | try { 124 | signal.await(30, TimeUnit.SECONDS); 125 | } 126 | catch (InterruptedException e) { 127 | } 128 | return signal.getCount() == 0; 129 | } 130 | 131 | public Throwable getUncaughtException() { 132 | return uncaughtException; 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/androidTest/java/com/strongloop/android/remoting/test/BeanUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.remoting.test; 2 | 3 | import android.test.MoreAsserts; 4 | 5 | import com.google.common.collect.ImmutableSet; 6 | import com.google.common.util.concurrent.MoreExecutors; 7 | import com.strongloop.android.remoting.BeanUtil; 8 | import com.strongloop.android.remoting.Repository; 9 | import com.strongloop.android.remoting.Transient; 10 | import com.strongloop.android.remoting.VirtualObject; 11 | 12 | import junit.framework.TestCase; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | public class BeanUtilTest extends TestCase { 18 | 19 | public static class Bean extends VirtualObject { 20 | private String name; 21 | private int age; 22 | private long score; 23 | private float weight; 24 | private double answerToAgeOfUniverse; 25 | private boolean totallyCool; 26 | 27 | // NOTE: Auto-generated getters, setters, and equals 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | public void setName(String name) { 33 | this.name = name; 34 | } 35 | 36 | public int getAge() { 37 | return age; 38 | } 39 | 40 | public void setAge(int age) { 41 | this.age = age; 42 | } 43 | 44 | public long getScore() { 45 | return score; 46 | } 47 | 48 | public void setScore(long score) { 49 | this.score = score; 50 | } 51 | 52 | public float getWeight() { 53 | return weight; 54 | } 55 | 56 | public void setWeight(float weight) { 57 | this.weight = weight; 58 | } 59 | 60 | public double getAnswerToAgeOfUniverse() { 61 | return answerToAgeOfUniverse; 62 | } 63 | 64 | public void setAnswerToAgeOfUniverse(double answerToAgeOfUniverse) { 65 | this.answerToAgeOfUniverse = answerToAgeOfUniverse; 66 | } 67 | 68 | public boolean isTotallyCool() { 69 | return totallyCool; 70 | } 71 | 72 | public void setTotallyCool(boolean totallyCool) { 73 | this.totallyCool = totallyCool; 74 | } 75 | 76 | @Override 77 | public int hashCode() { 78 | final int prime = 31; 79 | int result = 1; 80 | result = prime * result + age; 81 | long temp; 82 | temp = Double.doubleToLongBits(answerToAgeOfUniverse); 83 | result = prime * result + (int) (temp ^ (temp >>> 32)); 84 | result = prime * result + ((name == null) ? 0 : name.hashCode()); 85 | result = prime * result + (int) (score ^ (score >>> 32)); 86 | result = prime * result + (totallyCool ? 1231 : 1237); 87 | result = prime * result + Float.floatToIntBits(weight); 88 | return result; 89 | } 90 | 91 | @Override 92 | public boolean equals(Object obj) { 93 | if (this == obj) 94 | return true; 95 | if (obj == null) 96 | return false; 97 | if (getClass() != obj.getClass()) 98 | return false; 99 | Bean other = (Bean) obj; 100 | if (age != other.age) 101 | return false; 102 | if (Double.doubleToLongBits(answerToAgeOfUniverse) != Double 103 | .doubleToLongBits(other.answerToAgeOfUniverse)) 104 | return false; 105 | if (name == null) { 106 | if (other.name != null) 107 | return false; 108 | } else if (!name.equals(other.name)) 109 | return false; 110 | if (score != other.score) 111 | return false; 112 | if (totallyCool != other.totallyCool) 113 | return false; 114 | if (Float.floatToIntBits(weight) != Float 115 | .floatToIntBits(other.weight)) 116 | return false; 117 | return true; 118 | } 119 | 120 | public Bean() { 121 | super(new Repository("Bean"), new HashMap()); 122 | } 123 | } 124 | 125 | public static class Trans extends VirtualObject { 126 | private String trans; 127 | 128 | @Transient 129 | public String getTrans() { return trans; } 130 | 131 | @Transient 132 | public void setTrans(String trans) { this.trans = trans; } 133 | } 134 | 135 | public void testBean() { 136 | Bean fromBean = new Bean(); 137 | fromBean.setName("Fred"); 138 | fromBean.setAge(100); 139 | fromBean.setTotallyCool(true); 140 | fromBean.setAnswerToAgeOfUniverse(42); 141 | fromBean.setWeight(9000); 142 | fromBean.setScore(-100); 143 | 144 | // Convert to properties map and back to the bean, then test for 145 | // equality. Tests auto-boxing as well. 146 | // 147 | // NOTE: Not testing deep copy because it is not used in LoopBack, 148 | // and there is no analog for setProperties. 149 | Bean toBean = new Bean(); 150 | Map properties = BeanUtil.getProperties(fromBean, 151 | true, false); 152 | BeanUtil.setProperties(toBean, properties, true); 153 | assertEquals(fromBean, toBean); 154 | 155 | // Test Map with different types (short->int, etc). 156 | properties = new HashMap(); 157 | properties.put("name", "Fred"); 158 | properties.put("age", (short)100); 159 | properties.put("totallyCool", true); 160 | properties.put("answerToAgeOfUniverse", (long)42); 161 | properties.put("weight", (int)9000); 162 | properties.put("score", (int)-100); 163 | 164 | Bean bean3 = new Bean(); 165 | BeanUtil.setProperties(bean3, properties, true); 166 | assertEquals(fromBean, bean3); 167 | } 168 | 169 | public void testTransient() { 170 | Trans source = new Trans(); 171 | source.setTrans("transient value"); 172 | 173 | Map properties = BeanUtil.getProperties(source, false, true); 174 | MoreAsserts.assertEquals( 175 | "getProperties() should have ignored @Transient properties", 176 | new HashMap().entrySet(), properties.entrySet()); 177 | 178 | properties.put("trans", source.getTrans()); 179 | 180 | Trans target = new Trans(); 181 | BeanUtil.setProperties(target, properties, true); 182 | assertNull( 183 | "setProperties() should have ignored @Transient properties", 184 | target.getTrans()); 185 | } 186 | 187 | public void testGetPropertiesReturnsOwnPropertiesOnly() { 188 | Bean bean = new Bean(); 189 | Map ownProperties = BeanUtil.getProperties(bean, 190 | false, false); 191 | 192 | assertEquals( 193 | ImmutableSet.of("score", "totallyCool", "weight", 194 | "name", "answerToAgeOfUniverse", "age"), 195 | ownProperties.keySet()); 196 | } 197 | 198 | public void testVirtualObjectHasTransientPropertiesOnly() { 199 | VirtualObject obj = new VirtualObject(); 200 | MoreAsserts.assertEmpty(BeanUtil.getProperties(obj, true, true)); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/androidTest/java/com/strongloop/android/remoting/test/RestAdapterTest.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.remoting.test; 2 | 3 | import com.strongloop.android.remoting.Repository; 4 | import com.strongloop.android.remoting.VirtualObject; 5 | import com.strongloop.android.remoting.adapters.RestAdapter; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class RestAdapterTest extends AsyncTestCase { 11 | 12 | /** 13 | * Convenience method to create a single-entry Map. 14 | */ 15 | public static Map param(String name, T value) { 16 | Map params = new HashMap(); 17 | params.put(name, value); 18 | return params; 19 | } 20 | 21 | private RestAdapter adapter; 22 | private Repository testClass; 23 | 24 | @Override 25 | protected void setUp() throws Exception { 26 | super.setUp(); 27 | // NOTE: "10.0.2.2" is the "localhost" of the Android emulator's 28 | // host computer. 29 | adapter = createRestAdapter(); 30 | testClass = new Repository("SimpleClass"); 31 | testClass.setAdapter(adapter); 32 | } 33 | 34 | public void testConnected() { 35 | assertTrue(adapter.isConnected()); 36 | } 37 | 38 | public void testGet() throws Throwable { 39 | doAsyncTest(new AsyncTest() { 40 | 41 | @Override 42 | public void run() { 43 | adapter.invokeStaticMethod("simple.getSecret", null, 44 | expectJsonResponse("shhh!")); 45 | } 46 | }); 47 | } 48 | 49 | public void testTransform() throws Throwable { 50 | doAsyncTest(new AsyncTest() { 51 | 52 | @Override 53 | public void run() { 54 | adapter.invokeStaticMethod("simple.transform", 55 | param("str", "somevalue"), 56 | expectJsonResponse("transformed: somevalue")); 57 | } 58 | }); 59 | } 60 | 61 | 62 | public void testTestClassGet() throws Throwable { 63 | doAsyncTest(new AsyncTest() { 64 | 65 | @Override 66 | public void run() { 67 | adapter.invokeInstanceMethod("SimpleClass.prototype.getName", 68 | param("name", "somename"), 69 | null, 70 | expectJsonResponse("somename")); 71 | } 72 | }); 73 | } 74 | 75 | public void testTestClassTransform() throws Throwable { 76 | doAsyncTest(new AsyncTest() { 77 | 78 | @Override 79 | public void run() { 80 | adapter.invokeInstanceMethod("SimpleClass.prototype.greet", 81 | param("name", "somename"), 82 | param("other", "othername"), 83 | expectJsonResponse("Hi, othername!")); 84 | } 85 | }); 86 | } 87 | 88 | public void testPrototypeStatic() throws Throwable { 89 | doAsyncTest(new AsyncTest() { 90 | 91 | @Override 92 | public void run() { 93 | testClass.invokeStaticMethod("getFavoritePerson", null, 94 | expectJsonResponse("You")); 95 | } 96 | }); 97 | } 98 | 99 | public void testPrototypeGet() throws Throwable { 100 | doAsyncTest(new AsyncTest() { 101 | 102 | @Override 103 | public void run() { 104 | VirtualObject test = testClass.createObject( 105 | param("name", "somename")); 106 | test.invokeMethod("getName", null, 107 | expectJsonResponse("somename")); 108 | } 109 | }); 110 | } 111 | 112 | public void testPrototypeTransform() throws Throwable { 113 | doAsyncTest(new AsyncTest() { 114 | 115 | @Override 116 | public void run() { 117 | VirtualObject test = testClass.createObject( 118 | param("name", param("somekey", "somevalue"))); 119 | test.invokeMethod("greet", 120 | param("other", "othername"), 121 | expectJsonResponse("Hi, othername!")); 122 | } 123 | }); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/androidTest/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strongloop-community/loopback-sdk-android/9e1e304a66b6bd0557623dc81a22a28d9fe7a62d/src/androidTest/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/androidTest/res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strongloop-community/loopback-sdk-android/9e1e304a66b6bd0557623dc81a22a28d9fe7a62d/src/androidTest/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /src/androidTest/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strongloop-community/loopback-sdk-android/9e1e304a66b6bd0557623dc81a22a28d9fe7a62d/src/androidTest/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/androidTest/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strongloop-community/loopback-sdk-android/9e1e304a66b6bd0557623dc81a22a28d9fe7a62d/src/androidTest/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/androidTest/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LoopBackTestTest 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/loopback/AccessToken.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback; 2 | 3 | public class AccessToken extends Model { 4 | 5 | private Object userId; 6 | 7 | public void setUserId(Object userId) { 8 | this.userId = userId; 9 | } 10 | 11 | public Object getUserId() { return userId; } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/loopback/AccessTokenRepository.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback; 2 | 3 | 4 | public class AccessTokenRepository extends ModelRepository { 5 | public AccessTokenRepository() { 6 | super("accessToken", AccessToken.class); 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/loopback/Container.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import com.strongloop.android.loopback.callbacks.ListCallback; 5 | import com.strongloop.android.loopback.callbacks.ObjectCallback; 6 | import com.strongloop.android.remoting.VirtualObject; 7 | 8 | 9 | public class Container extends VirtualObject { 10 | 11 | private String name; 12 | public void setName(String name) { 13 | this.name = name; 14 | } 15 | public String getName() { 16 | return name; 17 | } 18 | 19 | /** 20 | * Upload a new file 21 | * @param file Content of the file. 22 | * @param callback The callback to be executed when finished. 23 | */ 24 | public void upload(java.io.File file, ObjectCallback callback) { 25 | getFileRepository().upload(file, callback); 26 | } 27 | 28 | /** 29 | * Upload a new file 30 | * @param fileName The file name, must be unique within the container. 31 | * @param content Content of the file. 32 | * @param contentType Content type (optional). 33 | * @param callback The callback to be executed when finished. 34 | */ 35 | public void upload(String fileName, byte[] content, String contentType, 36 | ObjectCallback callback) { 37 | getFileRepository().upload(fileName, content, contentType, callback); 38 | } 39 | 40 | /** 41 | * Create a new File object associated with this container. 42 | * @param name The name of the file. 43 | * @return the object created 44 | */ 45 | public File createFileObject(String name) { 46 | return getFileRepository().createObject(ImmutableMap.of("name", name)); 47 | } 48 | 49 | /** 50 | * Get data of a File object. 51 | * @param fileName The name of the file. 52 | * @param callback The callback to be executed when finished. 53 | */ 54 | public void getFile(String fileName, ObjectCallback callback) { 55 | getFileRepository().get(fileName, callback); 56 | } 57 | 58 | /** 59 | * List all files in the container. 60 | * @param callback The callback to be executed when finished. 61 | */ 62 | public void getAllFiles(ListCallback callback) { 63 | getFileRepository().getAll(callback); 64 | } 65 | 66 | public FileRepository getFileRepository() { 67 | RestAdapter adapter = ((RestAdapter)getRepository().getAdapter()); 68 | FileRepository repo = adapter.createRepository(FileRepository.class); 69 | repo.setContainer(this); 70 | return repo; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/loopback/ContainerRepository.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import com.strongloop.android.loopback.callbacks.JsonArrayParser; 5 | import com.strongloop.android.loopback.callbacks.JsonObjectParser; 6 | import com.strongloop.android.loopback.callbacks.ListCallback; 7 | import com.strongloop.android.loopback.callbacks.ObjectCallback; 8 | import com.strongloop.android.remoting.adapters.Adapter; 9 | import com.strongloop.android.remoting.adapters.RestContract; 10 | import com.strongloop.android.remoting.adapters.RestContractItem; 11 | 12 | public class ContainerRepository extends RestRepository { 13 | 14 | private String getNameForRestUrl() { 15 | return "containers"; 16 | } 17 | 18 | public ContainerRepository() { 19 | super("container", Container.class); 20 | } 21 | 22 | /** 23 | * Creates a {@link RestContract} representing the user type's custom 24 | * routes. Used to extend an {@link Adapter} to support user. Calls 25 | * super {@link ModelRepository} createContract first. 26 | * 27 | * @return A {@link RestContract} for this model type. 28 | */ 29 | 30 | public RestContract createContract() { 31 | RestContract contract = super.createContract(); 32 | 33 | String className = getClassName(); 34 | 35 | final String basePath = "/" + getNameForRestUrl(); 36 | contract.addItem(new RestContractItem(basePath, "POST"), 37 | className + ".create"); 38 | 39 | contract.addItem(new RestContractItem(basePath, "GET"), 40 | className + ".getAll"); 41 | 42 | contract.addItem(new RestContractItem(basePath + "/:name", "GET"), 43 | className + ".get"); 44 | 45 | contract.addItem(new RestContractItem(basePath + "/:name", "DELETE"), 46 | className + ".prototype.remove"); 47 | 48 | return contract; 49 | } 50 | 51 | /** 52 | * Create a new container. 53 | * @param name The name of the container, must be unique. 54 | * @param callback The callback to be executed when finished. 55 | */ 56 | public void create(String name, ObjectCallback callback) { 57 | invokeStaticMethod("create", ImmutableMap.of("name", name), 58 | new JsonObjectParser(this, callback)); 59 | } 60 | 61 | /** 62 | * Get a named container 63 | * @param containerName The container name. 64 | * @param callback The callback to be executed when finished. 65 | */ 66 | public void get(String containerName, ObjectCallback callback) { 67 | invokeStaticMethod("get", ImmutableMap.of("name", containerName), 68 | new JsonObjectParser(this, callback)); 69 | } 70 | 71 | /** 72 | * List all containers. 73 | * @param callback The callback to be executed when finished. 74 | */ 75 | public void getAll(ListCallback callback) { 76 | invokeStaticMethod("getAll", null, 77 | new JsonArrayParser(this, callback)); 78 | } 79 | } -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/loopback/File.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import com.google.common.io.Files; 5 | import com.strongloop.android.loopback.callbacks.EmptyResponseParser; 6 | import com.strongloop.android.loopback.callbacks.VoidCallback; 7 | import com.strongloop.android.remoting.Transient; 8 | import com.strongloop.android.remoting.VirtualObject; 9 | import com.strongloop.android.remoting.adapters.Adapter; 10 | 11 | import java.io.IOException; 12 | import java.util.Map; 13 | 14 | public class File extends VirtualObject { 15 | 16 | private String name; 17 | public void setName(String name) { this.name = name; } 18 | 19 | /** 20 | * The name of the file, e.g. "image.gif" 21 | * @return the name 22 | */ 23 | public String getName() { return name; } 24 | 25 | private String url; 26 | public void setUrl(String url) { this.url = url; } 27 | 28 | /** 29 | * The URL of the file. 30 | * @return the URL 31 | */ 32 | public String getUrl() { 33 | return url; 34 | } 35 | 36 | private Container container; 37 | @Transient 38 | public void setContainerRef(Container container) { this.container = container; } 39 | @Transient 40 | public Container getContainerRef() { return container; } 41 | 42 | /** 43 | * Name of the container this file belongs to. 44 | * @return the container name 45 | */ 46 | public String getContainer() { return getContainerRef().getName(); } 47 | 48 | public static interface DownloadCallback { 49 | public void onSuccess(byte[] content, String contentType); 50 | public void onError(Throwable error); 51 | } 52 | 53 | /** 54 | * Download content of this file. 55 | * @param callback The callback to be executed when finished. 56 | */ 57 | public void download(final DownloadCallback callback) { 58 | invokeMethod("download", getCommonParams(), new Adapter.BinaryCallback() { 59 | @Override 60 | public void onSuccess(byte[] content, String contentType) { 61 | callback.onSuccess(content, contentType); 62 | } 63 | 64 | @Override 65 | public void onError(Throwable t) { 66 | callback.onError(t); 67 | } 68 | }); 69 | } 70 | 71 | /** 72 | * Download content of this file to a local file. 73 | * @param localFile Path to the local file. 74 | * @param callback The callback to be executed when finished. 75 | */ 76 | public void download(final java.io.File localFile, final VoidCallback callback) { 77 | download(new DownloadCallback() { 78 | @Override 79 | public void onSuccess(byte[] content, String contentType) { 80 | try { 81 | Files.write(content, localFile); 82 | callback.onSuccess(); 83 | } catch (IOException ex) { 84 | callback.onError(ex); 85 | } 86 | } 87 | 88 | @Override 89 | public void onError(Throwable error) { 90 | callback.onError(error); 91 | 92 | } 93 | }); 94 | } 95 | 96 | /** 97 | * Delete this file. 98 | * @param callback The callback to be executed when finished. 99 | */ 100 | public void delete(final VoidCallback callback) { 101 | invokeMethod("delete", getCommonParams(), new EmptyResponseParser(callback)); 102 | } 103 | 104 | private Map getCommonParams() { 105 | return ImmutableMap.of( 106 | "container", getContainer(), 107 | "name", getName()); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/loopback/FileRepository.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import com.strongloop.android.loopback.callbacks.JsonArrayParser; 5 | import com.strongloop.android.loopback.callbacks.JsonObjectParser; 6 | import com.strongloop.android.loopback.callbacks.ListCallback; 7 | import com.strongloop.android.loopback.callbacks.ObjectCallback; 8 | import com.strongloop.android.remoting.JsonUtil; 9 | import com.strongloop.android.remoting.adapters.Adapter; 10 | import com.strongloop.android.remoting.adapters.RestContract; 11 | import com.strongloop.android.remoting.adapters.RestContractItem; 12 | import com.strongloop.android.remoting.adapters.StreamParam; 13 | 14 | import org.json.JSONException; 15 | import org.json.JSONObject; 16 | 17 | import java.io.ByteArrayInputStream; 18 | import java.io.InputStream; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | 22 | public class FileRepository extends RestRepository { 23 | private final static String TAG = "FileRepository"; 24 | 25 | private Container container; 26 | 27 | public Container getContainer() { 28 | return container; 29 | } 30 | public String getContainerName() { 31 | return getContainer().getName(); 32 | } 33 | 34 | public void setContainer(Container value) { 35 | container = value; 36 | } 37 | 38 | public FileRepository() { 39 | super("file", File.class); 40 | } 41 | 42 | /** 43 | * Creates a {@link RestContract} representing the user type's custom 44 | * routes. Used to extend an {@link Adapter} to support user. Calls 45 | * super {@link ModelRepository} createContract first. 46 | * 47 | * @return A {@link RestContract} for this model type. 48 | */ 49 | 50 | public RestContract createContract() { 51 | RestContract contract = super.createContract(); 52 | 53 | String basePath = "/containers/:container"; 54 | String className = getClassName(); 55 | 56 | contract.addItem(new RestContractItem(basePath + "/files/:name", "GET"), 57 | className + ".get"); 58 | 59 | contract.addItem(new RestContractItem(basePath + "/files", "GET"), 60 | className + ".getAll"); 61 | 62 | contract.addItem( 63 | RestContractItem.createMultipart(basePath + "/upload", "POST"), 64 | className + ".upload"); 65 | 66 | contract.addItem(new RestContractItem(basePath + "/download/:name", "GET"), 67 | className + ".prototype.download"); 68 | 69 | contract.addItem(new RestContractItem(basePath + "/files/:name", "DELETE"), 70 | className + ".prototype.delete"); 71 | 72 | return contract; 73 | } 74 | 75 | @Override 76 | public File createObject(Map parameters) { 77 | File file = super.createObject(parameters); 78 | file.setContainerRef(container); 79 | return file; 80 | } 81 | 82 | /** 83 | * Upload a new file 84 | * @param name The file name, must be unique within the container. 85 | * @param content Content of the file. 86 | * @param contentType Content type (optional). 87 | * @param callback The callback to be executed when finished. 88 | */ 89 | public void upload(String name, byte[] content, String contentType, 90 | ObjectCallback callback) { 91 | upload(name, new ByteArrayInputStream(content), contentType, callback); 92 | } 93 | 94 | /** 95 | * Upload a new file 96 | * @param name The file name, must be unique within the container. 97 | * @param content Content of the file. 98 | * @param contentType Content type (optional). 99 | * @param callback The callback to be executed when finished. 100 | */ 101 | public void upload(String name, InputStream content, String contentType, 102 | final ObjectCallback callback) { 103 | 104 | StreamParam param = new StreamParam(content, name, contentType); 105 | invokeStaticMethod("upload", 106 | ImmutableMap.of("container", getContainerName(), "file", param), 107 | new UploadResponseParser(this, callback)); 108 | } 109 | 110 | /** 111 | * Upload a new file 112 | * @param localFile The local file to upload. 113 | * @param callback The callback to be executed when finished. 114 | */ 115 | public void upload(java.io.File localFile, final ObjectCallback callback) { 116 | invokeStaticMethod("upload", 117 | ImmutableMap.of("container", getContainerName(), "file", localFile), 118 | new UploadResponseParser(this, callback)); 119 | } 120 | 121 | /** 122 | * Get file by name 123 | * @param name The name of the file to get. 124 | * @param callback The callback to be executed when finished. 125 | */ 126 | public void get(String name, final ObjectCallback callback) { 127 | final HashMap params = new HashMap(); 128 | 129 | params.put("container", getContainerName()); 130 | params.put("name", name); 131 | invokeStaticMethod("get", params, 132 | new JsonObjectParser(this, callback)); 133 | } 134 | 135 | /** 136 | * List all files in the container. 137 | * @param callback The callback to be executed when finished. 138 | */ 139 | public void getAll(ListCallback callback) { 140 | invokeStaticMethod("getAll", 141 | ImmutableMap.of("container", getContainerName()), 142 | new JsonArrayParser(this, callback)); 143 | } 144 | 145 | private class UploadResponseParser extends Adapter.JsonObjectCallback { 146 | private final FileRepository repository; 147 | private final ObjectCallback callback; 148 | 149 | private UploadResponseParser(FileRepository repository, ObjectCallback callback) { 150 | this.repository = repository; 151 | this.callback = callback; 152 | } 153 | 154 | @Override 155 | public void onSuccess(JSONObject response) { 156 | try { 157 | JSONObject data = response.getJSONObject("result") 158 | .getJSONObject("files") 159 | .getJSONArray("file") 160 | .getJSONObject(0); 161 | callback.onSuccess( 162 | repository.createObject(JsonUtil.fromJson(data))); 163 | } catch (JSONException e) { 164 | callback.onError(e); 165 | } 166 | } 167 | 168 | @Override 169 | public void onError(Throwable t) { 170 | callback.onError(t); 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/loopback/Model.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 StrongLoop. All rights reserved. 2 | 3 | package com.strongloop.android.loopback; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import org.json.JSONObject; 9 | 10 | import com.strongloop.android.loopback.callbacks.VoidCallback; 11 | import com.strongloop.android.remoting.Repository; 12 | import com.strongloop.android.remoting.VirtualObject; 13 | import com.strongloop.android.remoting.adapters.Adapter; 14 | 15 | /** 16 | * A local representative of a single model instance on the server. The data is 17 | * immediately accessible locally, but can be saved, destroyed, etc. from the 18 | * server easily. 19 | */ 20 | public class Model extends VirtualObject { 21 | 22 | /** 23 | * @deprecated Use {link VoidCallback} instead. 24 | */ 25 | public static interface Callback extends VoidCallback { 26 | } 27 | 28 | private Object id; 29 | private Map overflow = new HashMap(); 30 | 31 | public Model(Repository repository, 32 | Map creationParameters) { 33 | super(repository, creationParameters); 34 | } 35 | 36 | public Model() { 37 | this(null, null); 38 | } 39 | 40 | /** 41 | * Gets the model's id field. 42 | * @return The id. 43 | */ 44 | public Object getId() { 45 | return id; 46 | } 47 | 48 | /* package private */ void setId(Object id) { 49 | this.id = id; 50 | } 51 | 52 | /** 53 | * Gets the value associated with a given key. 54 | * @param key The key for which to return the corresponding value. 55 | * @return The value associated with the key, or null if no 56 | * value is associated with the key. 57 | */ 58 | public Object get(String key) { 59 | return overflow.get(key); 60 | } 61 | 62 | /** 63 | * Adds a given key-value pair to the dictionary. 64 | * 65 | * @param key The key for value. If the key already exists 66 | * in the dictionary, the specified value takes its place. 67 | * @param value The value for the key. The value may be null. 68 | */ 69 | public void put(String key, Object value) { 70 | overflow.put(key, value); 71 | } 72 | 73 | /** 74 | * Adds all the specified params to the dictionary. 75 | * @param params The params to add. 76 | */ 77 | public void putAll(Map params) { 78 | overflow.putAll(params); 79 | } 80 | 81 | /** 82 | * Converts the Model (and all of its Java Bean properties) into a 83 | * {@link java.util.Map}. 84 | */ 85 | public Map toMap() { 86 | Map map = new HashMap(); 87 | map.putAll(overflow); 88 | map.put("id", getId()); 89 | map.putAll(super.toMap()); 90 | return map; 91 | } 92 | 93 | /** 94 | * Saves the Model to the server. 95 | *

96 | * This method calls {@link #toMap()} to determine which fields should be 97 | * saved. 98 | * @param callback The callback to be executed when finished. 99 | */ 100 | public void save(final VoidCallback callback) { 101 | invokeMethod(id == null ? "create" : "save", toMap(), 102 | new Adapter.JsonObjectCallback() { 103 | 104 | @Override 105 | public void onError(Throwable t) { 106 | callback.onError(t); 107 | } 108 | 109 | @Override 110 | public void onSuccess(JSONObject response) { 111 | Object id = response.opt("id"); 112 | if (id != null) { 113 | setId(id); 114 | } 115 | callback.onSuccess(); 116 | } 117 | }); 118 | } 119 | 120 | /** 121 | * Destroys the Model from the server. 122 | * @param callback The callback to be executed when finished. 123 | */ 124 | public void destroy(final VoidCallback callback) { 125 | invokeMethod("remove", toMap(), new Adapter.Callback() { 126 | 127 | @Override 128 | public void onError(Throwable t) { 129 | callback.onError(t); 130 | } 131 | 132 | @Override 133 | public void onSuccess(String response) { 134 | callback.onSuccess(); 135 | } 136 | }); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/loopback/ModelRepository.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 StrongLoop. All rights reserved. 2 | 3 | package com.strongloop.android.loopback; 4 | 5 | import com.strongloop.android.loopback.callbacks.JsonArrayParser; 6 | import com.strongloop.android.loopback.callbacks.JsonObjectParser; 7 | import com.strongloop.android.loopback.callbacks.ListCallback; 8 | import com.strongloop.android.loopback.callbacks.ObjectCallback; 9 | import com.strongloop.android.remoting.adapters.Adapter; 10 | import com.strongloop.android.remoting.adapters.RestContract; 11 | import com.strongloop.android.remoting.adapters.RestContractItem; 12 | 13 | import org.atteo.evo.inflector.English; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | /** 19 | * A local representative of a single model type on the server, encapsulating 20 | * the name of the model type for easy {@link Model} creation, discovery, and 21 | * management. 22 | */ 23 | public class ModelRepository extends RestRepository { 24 | 25 | /** 26 | * @deprecated Use {link ObjectCallback} instead. 27 | */ 28 | public interface FindCallback extends ObjectCallback { 29 | } 30 | 31 | /** 32 | * @deprecated Use {link ListCallback} instead. 33 | */ 34 | public interface FindAllCallback extends ListCallback { 35 | } 36 | 37 | private String nameForRestUrl; 38 | 39 | public ModelRepository(String className) { 40 | this(className, null); 41 | } 42 | 43 | /** 44 | * Creates a new Repository, associating it with the named remote class. 45 | * @param className The remote class name. 46 | * @param modelClass The Model class. It must have a public no-argument 47 | * constructor. 48 | */ 49 | public ModelRepository(String className, Class modelClass) { 50 | this(className, null, modelClass); 51 | } 52 | 53 | /** 54 | * Creates a new Repository, associating it with the named remote class. 55 | * @param className The remote class name. 56 | * @param nameForRestUrl The pluralized class name to use in REST transport. 57 | * Use {@code null} for the default value, which is the plural 58 | * form of className. 59 | * @param modelClass The Model class. It must have a public no-argument 60 | * constructor. 61 | */ 62 | @SuppressWarnings("unchecked") 63 | public ModelRepository(String className, String nameForRestUrl, Class modelClass) { 64 | super(className, modelClass != null ? modelClass : (Class)Model.class); 65 | 66 | this.nameForRestUrl = nameForRestUrl != null 67 | ? nameForRestUrl 68 | : English.plural(className); 69 | } 70 | 71 | /** 72 | * Returns the name of the REST url 73 | * @return nameForRestUrl 74 | */ 75 | public String getNameForRestUrl() { 76 | return nameForRestUrl; 77 | } 78 | 79 | /** 80 | * Creates a {@link RestContract} representing this model type's custom 81 | * routes. Used to extend an {@link Adapter} to support this model type. 82 | * 83 | * @return A {@link RestContract} for this model type. 84 | */ 85 | public RestContract createContract() { 86 | RestContract contract = super.createContract(); 87 | 88 | String className = getClassName(); 89 | 90 | contract.addItem(new RestContractItem("/" + nameForRestUrl, "POST"), 91 | className + ".prototype.create"); 92 | contract.addItem(new RestContractItem("/" + nameForRestUrl + "/:id", "PUT"), 93 | className + ".prototype.save"); 94 | contract.addItem( 95 | new RestContractItem("/" + nameForRestUrl + "/:id", "DELETE"), 96 | className + ".prototype.remove"); 97 | contract.addItem(new RestContractItem("/" + nameForRestUrl + "/:id", "GET"), 98 | className + ".findById"); 99 | contract.addItem(new RestContractItem("/" + nameForRestUrl + "/findOne", "GET"), 100 | className + ".findOne"); 101 | contract.addItem(new RestContractItem("/" + nameForRestUrl, "GET"), 102 | className + ".all"); 103 | 104 | return contract; 105 | } 106 | 107 | /** 108 | * @deprecated Use {link ModelRepository#createObject} instead. 109 | */ 110 | public T createModel(Map parameters) { 111 | return createObject(parameters); 112 | } 113 | 114 | /** 115 | * Creates a new {@link Model} of this type with the parameters described. 116 | * @param parameters The parameters. 117 | * @return A new {@link Model}. 118 | */ 119 | @Override 120 | public T createObject(Map parameters) { 121 | T model = super.createObject(parameters); 122 | model.putAll(parameters); 123 | 124 | Object id = parameters.get("id"); 125 | if (id != null) { 126 | model.setId(id); 127 | } 128 | 129 | return model; 130 | } 131 | 132 | /** 133 | * Finds and downloads a single instance of this model type on and from the 134 | * server with the given id. 135 | * @param id The id to search for. 136 | * @param callback The callback to be executed when finished. 137 | */ 138 | public void findById(Object id, final ObjectCallback callback) { 139 | Map params = new HashMap(); 140 | params.put("id", id); 141 | invokeStaticMethod("findById", params, 142 | new JsonObjectParser(this, callback)); 143 | } 144 | 145 | /** 146 | * Finds and downloads all models of this type on and from the server. 147 | * @param callback The callback to be executed when finished. 148 | */ 149 | public void findAll(final ListCallback callback) { 150 | find(null, callback); 151 | } 152 | 153 | /** 154 | * Finds and downloads all models of this type on and from the server. 155 | * that match the specified filter 156 | * @param parameters filter. 157 | * @param callback The callback to be executed when finished. 158 | */ 159 | public void find(Map parameters, final ListCallback callback) { 160 | invokeStaticMethod("all", 161 | parameters, 162 | new JsonArrayParser(this, callback)); 163 | } 164 | 165 | 166 | /** 167 | * Finds and downloads the first model of this type on and from the server. 168 | * that match the specified filter 169 | * @param parameters filter. 170 | * @param callback The callback to be executed when finished. 171 | */ 172 | public void findOne(Map parameters, final ObjectCallback callback) { 173 | invokeStaticMethod("findOne", 174 | parameters, 175 | new JsonObjectParser(this, callback)); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/loopback/RestAdapter.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 StrongLoop. All rights reserved. 2 | 3 | package com.strongloop.android.loopback; 4 | 5 | import android.content.Context; 6 | import android.content.SharedPreferences; 7 | 8 | /** 9 | * An extension to the vanilla 10 | * {@link com.strongloop.android.remoting.adapters.RestAdapter} 11 | * to make working with 12 | * {@link Model}s easier. 13 | */ 14 | public class RestAdapter 15 | extends com.strongloop.android.remoting.adapters.RestAdapter { 16 | 17 | public static final String SHARED_PREFERENCES_NAME = 18 | RestAdapter.class.getCanonicalName(); 19 | public static final String PROPERTY_ACCESS_TOKEN = "accessToken"; 20 | 21 | private final Context context; 22 | 23 | public RestAdapter(Context context, String url) { 24 | super(context, url); 25 | if (context == null) throw new NullPointerException("context must be not null"); 26 | this.context = context; 27 | setAccessToken(loadAccessToken()); 28 | } 29 | 30 | public void setAccessToken(String accessToken) { 31 | saveAccessToken(accessToken); 32 | getClient().addHeader("Authorization", accessToken); 33 | } 34 | 35 | public void clearAccessToken() { 36 | getClient().addHeader("Authorization", null); 37 | } 38 | 39 | public Context getApplicationContext() { 40 | return context; 41 | } 42 | 43 | /** 44 | * Creates a new {@link ModelRepository} representing the named model type. 45 | * @param name The model name. 46 | * @return A new repository instance. 47 | */ 48 | public ModelRepository createRepository(String name) { 49 | return createRepository(name, null, null); 50 | } 51 | 52 | /** 53 | * Creates a new {@link ModelRepository} representing the named model type. 54 | * @param name The model name. 55 | * @param nameForRestUrl The model name to use in REST URL, usually the plural form of `name`. 56 | * @return A new repository instance. 57 | */ 58 | public ModelRepository createRepository(String name, String nameForRestUrl) { 59 | return createRepository(name, nameForRestUrl, null); 60 | } 61 | 62 | /** 63 | * Creates a new {@link ModelRepository} representing the named model type. 64 | * @param name The model name. 65 | * @param nameForRestUrl The model name to use in REST URL, usually the plural form of `name`. 66 | * @param modelClass The model class. The class must have a public 67 | * no-argument constructor. 68 | * @return A new repository instance. 69 | */ 70 | public ModelRepository createRepository(String name, 71 | String nameForRestUrl, 72 | Class modelClass) { 73 | ModelRepository repository = new ModelRepository(name, nameForRestUrl, modelClass); 74 | attachModelRepository(repository); 75 | return repository; 76 | } 77 | 78 | /** 79 | * Creates a new {@link ModelRepository} from the given subclass. 80 | * @param repositoryClass A subclass of {@link ModelRepository} to use. 81 | * The class must have a public no-argument constructor. 82 | * @return A new repository instance. 83 | */ 84 | public U createRepository( 85 | Class repositoryClass) { 86 | U repository = null; 87 | try { 88 | repository = repositoryClass.newInstance(); 89 | repository.setAdapter(this); 90 | } 91 | catch (Exception e) { 92 | IllegalArgumentException ex = new IllegalArgumentException(); 93 | ex.initCause(e); 94 | throw ex; 95 | } 96 | attachModelRepository(repository); 97 | return repository; 98 | } 99 | 100 | 101 | private void attachModelRepository(RestRepository repository) { 102 | getContract().addItemsFromContract(repository.createContract()); 103 | repository.setAdapter(this); 104 | } 105 | 106 | private void saveAccessToken(String accessToken) { 107 | final SharedPreferences.Editor editor = getSharedPreferences().edit(); 108 | editor.putString(PROPERTY_ACCESS_TOKEN, accessToken); 109 | editor.commit(); 110 | } 111 | 112 | private String loadAccessToken() { 113 | return getSharedPreferences().getString(PROPERTY_ACCESS_TOKEN, null); 114 | } 115 | 116 | private SharedPreferences getSharedPreferences() { 117 | return context.getSharedPreferences( 118 | SHARED_PREFERENCES_NAME, 119 | Context.MODE_PRIVATE); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/loopback/RestRepository.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback; 2 | 3 | import android.content.Context; 4 | 5 | import com.strongloop.android.remoting.Repository; 6 | import com.strongloop.android.remoting.VirtualObject; 7 | import com.strongloop.android.remoting.adapters.RestContract; 8 | 9 | public class RestRepository extends Repository{ 10 | public RestRepository(String className) { 11 | super(className); 12 | } 13 | 14 | public RestRepository(String className, Class objectClass) { 15 | super(className, objectClass); 16 | } 17 | 18 | public RestContract createContract() { 19 | return new RestContract(); 20 | } 21 | 22 | public RestAdapter getRestAdapter() { 23 | return (RestAdapter) getAdapter(); 24 | } 25 | 26 | protected Context getApplicationContext() { 27 | return getRestAdapter().getApplicationContext(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/loopback/User.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback; 2 | 3 | import java.util.Map; 4 | 5 | import com.strongloop.android.remoting.Repository; 6 | 7 | /** 8 | * A local representative of a single user instance on the server. Derived from Model, 9 | * the data is immediately accessible locally, but can be saved, destroyed, etc. from the 10 | * server easily. 11 | */ 12 | 13 | public class User extends Model { 14 | 15 | private String realm; 16 | private String email; 17 | private String password; 18 | private Boolean emailVerified; 19 | private String status; 20 | 21 | public User(Repository repository, 22 | Map creationParameters) { 23 | super(repository, creationParameters); 24 | } 25 | 26 | public User() { 27 | this(null, null); 28 | } 29 | 30 | public void setRealm(String realm) { 31 | this.realm = realm; 32 | } 33 | 34 | public String getRealm() { 35 | return realm; 36 | } 37 | 38 | public void setEmail(String email) { 39 | this.email = email; 40 | } 41 | 42 | public String getEmail() { 43 | return email; 44 | } 45 | 46 | public void setPassword(String password) { 47 | this.password = password; 48 | } 49 | 50 | public String getPassword() { 51 | return password; 52 | } 53 | 54 | public void setEmailVerified(Boolean emailVerified) { 55 | this.emailVerified = emailVerified; 56 | } 57 | 58 | public Boolean getEmailVerified() { 59 | return emailVerified; 60 | } 61 | 62 | public void setStatus(String status) { 63 | this.status = status; 64 | } 65 | 66 | public String getStatus() { 67 | return status; 68 | } 69 | 70 | 71 | } 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/loopback/callbacks/EmptyResponseParser.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback.callbacks; 2 | 3 | import com.strongloop.android.remoting.adapters.Adapter; 4 | 5 | public class EmptyResponseParser implements Adapter.Callback { 6 | private final VoidCallback callback; 7 | 8 | public EmptyResponseParser(VoidCallback callback) { 9 | this.callback = callback; 10 | } 11 | 12 | @Override 13 | public void onSuccess(String response) { 14 | callback.onSuccess(); 15 | } 16 | 17 | @Override 18 | public void onError(Throwable t) { 19 | callback.onError(t); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/loopback/callbacks/JsonArrayParser.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback.callbacks; 2 | 3 | import com.strongloop.android.remoting.JsonUtil; 4 | import com.strongloop.android.remoting.Repository; 5 | import com.strongloop.android.remoting.VirtualObject; 6 | import com.strongloop.android.remoting.adapters.Adapter; 7 | 8 | import org.json.JSONArray; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class JsonArrayParser extends Adapter.JsonArrayCallback { 14 | private final Repository repository; 15 | private final ListCallback callback; 16 | 17 | public JsonArrayParser(Repository repository, ListCallback callback) { 18 | this.repository = repository; 19 | this.callback = callback; 20 | } 21 | 22 | @Override 23 | public void onSuccess(JSONArray response) { 24 | List list = new ArrayList(); 25 | if (response != null) { 26 | for (int i = 0; i < response.length(); i++) { 27 | list.add(repository.createObject(JsonUtil.fromJson( 28 | response.optJSONObject(i)))); 29 | } 30 | } 31 | callback.onSuccess(list); 32 | } 33 | 34 | @Override 35 | public void onError(Throwable throwable) { 36 | callback.onError(throwable); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/loopback/callbacks/JsonObjectParser.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback.callbacks; 2 | 3 | import com.strongloop.android.remoting.JsonUtil; 4 | import com.strongloop.android.remoting.Repository; 5 | import com.strongloop.android.remoting.VirtualObject; 6 | import com.strongloop.android.remoting.adapters.Adapter; 7 | 8 | import org.json.JSONObject; 9 | 10 | public class JsonObjectParser 11 | extends Adapter.JsonObjectCallback { 12 | 13 | private final Repository repository; 14 | private final ObjectCallback callback; 15 | 16 | public JsonObjectParser(Repository repository, ObjectCallback callback) { 17 | this.repository = repository; 18 | this.callback = callback; 19 | } 20 | 21 | @Override 22 | public void onSuccess(JSONObject response) { 23 | if (response == null) { 24 | // Not found 25 | callback.onSuccess(null); 26 | return; 27 | } 28 | 29 | callback.onSuccess( 30 | repository.createObject(JsonUtil.fromJson(response))); 31 | } 32 | 33 | @Override 34 | public void onError(Throwable throwable) { 35 | callback.onError(throwable); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/loopback/callbacks/ListCallback.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback.callbacks; 2 | 3 | import com.strongloop.android.remoting.VirtualObject; 4 | 5 | import java.util.List; 6 | 7 | public interface ListCallback { 8 | public void onSuccess(List objects); 9 | public void onError(Throwable t); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/loopback/callbacks/ObjectCallback.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback.callbacks; 2 | 3 | import com.strongloop.android.remoting.VirtualObject; 4 | 5 | public interface ObjectCallback { 6 | public void onSuccess(T object); 7 | public void onError(Throwable t); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/loopback/callbacks/VoidCallback.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.loopback.callbacks; 2 | 3 | public interface VoidCallback { 4 | public void onSuccess(); 5 | public void onError(Throwable t); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/remoting/JsonUtil.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 StrongLoop. All rights reserved. 2 | 3 | package com.strongloop.android.remoting; 4 | 5 | import java.lang.reflect.Array; 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.Iterator; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | import org.json.JSONArray; 13 | import org.json.JSONException; 14 | import org.json.JSONObject; 15 | 16 | /** 17 | * Utility methods for converting JSON objects to Java collection objects 18 | * (and vice versa). 19 | */ 20 | public class JsonUtil { 21 | 22 | /** 23 | * Converts a Java object to a JSON object. A {@link java.util.Map} is 24 | * converted to a {@link org.json.JSONObject}, a {@link java.util.List} or 25 | * array is converted to a {@link org.json.JSONArray}, 26 | * and null is converted to {@link org.json.JSONObject#NULL}. 27 | * Other objects, like {@link java.lang.Number}, {@link java.lang.String}, 28 | * and {@link java.lang.Boolean} are returned without conversion. 29 | * @param object The object to convert. 30 | * @return a JSON object. 31 | * @throws JSONException If the object cannot be converted. 32 | */ 33 | public static Object toJson(Object object) throws JSONException { 34 | if (object == null || object == JSONObject.NULL) { 35 | return JSONObject.NULL; 36 | } 37 | else if (object instanceof Map) { 38 | Map map = ((Map)object); 39 | JSONObject json = new JSONObject(); 40 | for (Map.Entry entry : map.entrySet()) { 41 | json.put(String.valueOf(entry.getKey()), 42 | toJson(entry.getValue())); 43 | } 44 | return json; 45 | } 46 | else if (object instanceof Iterable) { 47 | JSONArray json = new JSONArray(); 48 | for (Object value : (Iterable)object) { 49 | json.put(toJson(value)); 50 | } 51 | return json; 52 | } 53 | else if (object.getClass().isArray()) { 54 | JSONArray json = new JSONArray(); 55 | int length = Array.getLength(object); 56 | for (int i = 0; i < length; i++) { 57 | json.put(toJson(Array.get(object, i))); 58 | } 59 | return json; 60 | } 61 | else if (object instanceof Number) { 62 | double d = ((Number)object).doubleValue(); 63 | if (Double.isInfinite(d) || Double.isNaN(d)) { 64 | throw new JSONException("Numbers cannot be infinite or NaN."); 65 | } 66 | return object; 67 | } 68 | else if (object instanceof JSONObject || 69 | object instanceof JSONArray || 70 | object instanceof Boolean || 71 | object instanceof String) { 72 | return object; 73 | } 74 | else { 75 | return object.toString(); 76 | } 77 | } 78 | 79 | /** 80 | * Converts a {@link org.json.JSONObject} to a {@link java.util.Map}. 81 | * @param object The JSON object to convert. 82 | * @return a map, or null if the object is null. 83 | */ 84 | public static Map fromJson(JSONObject object) { 85 | if (object == null) { 86 | return null; 87 | } 88 | Map map = new HashMap(); 89 | if (object != null) { 90 | Iterator keys = object.keys(); 91 | while (keys.hasNext()) { 92 | String key = (String)keys.next(); 93 | map.put(key, fromJson(object.opt(key))); 94 | } 95 | } 96 | return map; 97 | } 98 | 99 | /** 100 | * Converts a {@link org.json.JSONArray} to a {@link java.util.List}. 101 | * @param array The JSON array to convert. 102 | * @return a list, or null if the array is null. 103 | */ 104 | public static List fromJson(JSONArray array) { 105 | if (array == null) { 106 | return null; 107 | } 108 | List list = new ArrayList(); 109 | if (array != null) { 110 | for (int i = 0; i < array.length(); i++) { 111 | list.add(fromJson(array.opt(i))); 112 | } 113 | } 114 | return list; 115 | } 116 | 117 | private static Object fromJson(Object json) { 118 | if (json == JSONObject.NULL) { 119 | return null; 120 | } 121 | else if (json instanceof JSONObject) { 122 | return fromJson((JSONObject)json); 123 | } 124 | else if (json instanceof JSONArray) { 125 | return fromJson((JSONArray)json); 126 | } 127 | else { 128 | return json; 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/remoting/Repository.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 StrongLoop. All rights reserved. 2 | 3 | package com.strongloop.android.remoting; 4 | 5 | import java.util.Map; 6 | 7 | import com.strongloop.android.remoting.adapters.Adapter; 8 | 9 | /** 10 | * A local representative of remote model repository, it provides 11 | * access to static methods like
User.findById()
. 12 | */ 13 | public class Repository { 14 | 15 | private final Class objectClass; 16 | private String className; 17 | private Adapter adapter; 18 | 19 | /** 20 | * Creates a new Repository, associating it with the named remote class. 21 | * @param className The remote class name. 22 | */ 23 | public Repository(String className) { 24 | this(className, null); 25 | } 26 | 27 | /** 28 | * Creates a new Repository, associating it with the named remote class. 29 | * @param className The remote class name. 30 | */ 31 | @SuppressWarnings("unchecked") 32 | public Repository(String className, Class objectClass) { 33 | if (className == null || className.length() == 0) { 34 | throw new IllegalArgumentException( 35 | "Class name cannot be null or empty."); 36 | } 37 | this.className = className; 38 | 39 | if (objectClass != null) 40 | this.objectClass = objectClass; 41 | else 42 | this.objectClass = (Class)VirtualObject.class; 43 | } 44 | 45 | /** 46 | * Gets the name given to this prototype on the server. 47 | * @return the class name. 48 | */ 49 | public String getClassName() { 50 | return className; 51 | } 52 | 53 | /** 54 | * Gets the {@link Adapter} that should be used for invoking methods, both 55 | * for static methods on this prototype and all methods on all instances of 56 | * this prototype. 57 | * @return the adapter. 58 | */ 59 | public Adapter getAdapter() { 60 | return adapter; 61 | } 62 | 63 | /** 64 | * Sets the {@link Adapter} that should be used for invoking methods, both 65 | * for static methods on this prototype and all methods on all instances of 66 | * this prototype. 67 | * @param adapter The adapter. 68 | */ 69 | public void setAdapter(Adapter adapter) { 70 | this.adapter = adapter; 71 | } 72 | 73 | /** 74 | * Creates a new {@link VirtualObject} as a virtual instance of this remote 75 | * class. 76 | * @param creationParameters The creation parameters of the new object. 77 | * @return A new {@link VirtualObject} based on this prototype. 78 | */ 79 | public T createObject( 80 | Map creationParameters) { 81 | T object = null; 82 | try { 83 | object = objectClass.newInstance(); 84 | } 85 | catch (Exception e) { 86 | IllegalArgumentException ex = new IllegalArgumentException(); 87 | ex.initCause(e); 88 | throw ex; 89 | } 90 | object.setRepository(this); 91 | if (creationParameters != null) { 92 | object.setCreationParameters(creationParameters); 93 | BeanUtil.setProperties(object, creationParameters, true); 94 | } 95 | return object; 96 | } 97 | 98 | /** 99 | * Invokes a remotable method exposed statically within this class on the 100 | * server. 101 | * @see Adapter#invokeStaticMethod(String, Map, 102 | * com.strongloop.android.remoting.adapters.Adapter.Callback) 103 | * @param method The method to invoke (without the class name), e.g. 104 | * "doSomething". 105 | * @param parameters The parameters to invoke with. 106 | * @param callback The callback to invoke when the execution finishes. 107 | */ 108 | public void invokeStaticMethod(String method, 109 | Map parameters, 110 | Adapter.Callback callback) { 111 | if (adapter == null) { 112 | throw new IllegalArgumentException("No adapter set"); 113 | } 114 | String path = className + "." + method; 115 | adapter.invokeStaticMethod(path, parameters, callback); 116 | } 117 | 118 | /** 119 | * Invokes a remotable method exposed statically within this class on the 120 | * server, 121 | * parses the response as binary data. 122 | * @see Adapter#invokeStaticMethod(String, Map, 123 | * com.strongloop.android.remoting.adapters.Adapter.Callback) 124 | * @param method The method to invoke (without the class name), e.g. 125 | * "doSomething". 126 | * @param parameters The parameters to invoke with. 127 | * @param callback The callback to invoke when the execution finishes. 128 | */ 129 | public void invokeStaticMethod(String method, 130 | Map parameters, 131 | Adapter.BinaryCallback callback) { 132 | if (adapter == null) { 133 | throw new IllegalArgumentException("No adapter set"); 134 | } 135 | String path = className + "." + method; 136 | adapter.invokeStaticMethod(path, parameters, callback); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/remoting/Transient.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.remoting; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * An annotation marking a class member as transient, 10 | * i.e. excluded from toMap/JSON serialization. 11 | */ 12 | @Target(ElementType.METHOD) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface Transient { 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/remoting/VirtualObject.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 StrongLoop. All rights reserved. 2 | 3 | package com.strongloop.android.remoting; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import com.strongloop.android.remoting.adapters.Adapter; 9 | 10 | /** 11 | * A local representative of a single virtual object. The behavior of this 12 | * object is defined through a model defined on the server, and the identity 13 | * of this instance is defined through its `creationParameters`. 14 | */ 15 | public class VirtualObject { 16 | 17 | private Repository repository; 18 | private Map creationParameters; 19 | 20 | /** 21 | * Creates a new object not attached to any repository. 22 | */ 23 | public VirtualObject() { 24 | this(null, null); 25 | } 26 | 27 | /** 28 | * Creates a new object from the given repository and parameters. 29 | * @param repository The repository this object should inherit from. 30 | * @param creationParameters The creationParameters of the new object. 31 | */ 32 | public VirtualObject(Repository repository, 33 | Map creationParameters) { 34 | setRepository(repository); 35 | setCreationParameters(creationParameters); 36 | } 37 | 38 | /** 39 | * Gets the {@link Repository} this object was created from. 40 | * @return the {@link Repository}. 41 | */ 42 | @Transient 43 | public Repository getRepository() { 44 | return repository; 45 | } 46 | 47 | /** 48 | * Sets the {@link Repository} this object was created from. 49 | * @param repository The {@link Repository}. 50 | */ 51 | @Transient 52 | public void setRepository(Repository repository) { 53 | this.repository = repository; 54 | } 55 | 56 | /** 57 | * Gets the creation parameters this object was created from. 58 | * @return the creation parameters. 59 | */ 60 | @Transient 61 | public Map getCreationParameters() { 62 | return creationParameters; 63 | } 64 | 65 | /** 66 | * Sets the creation parameters this object was created from. 67 | * @param creationParameters The creation parameters. 68 | */ 69 | @Transient 70 | public void setCreationParameters( 71 | Map creationParameters) { 72 | this.creationParameters = creationParameters; 73 | } 74 | 75 | 76 | /** 77 | * Converts the object (and all of its Java Bean properties) into a 78 | * {@link java.util.Map}. 79 | */ 80 | public Map toMap() { 81 | return BeanUtil.getProperties(this, false, false); 82 | } 83 | 84 | /** 85 | * Invokes a remotable method exposed within instances of this class on the 86 | * server. 87 | * @param method The method to invoke (without the repository), e.g. 88 | * "doSomething". 89 | * @param parameters The parameters to invoke with. 90 | * @param callback The callback to invoke when the execution finishes. 91 | */ 92 | public void invokeMethod(String method, 93 | Map parameters, 94 | Adapter.Callback callback) { 95 | Adapter adapter = repository.getAdapter(); 96 | if (adapter == null) { 97 | throw new IllegalArgumentException( 98 | "Repository adapter cannot be null"); 99 | } 100 | String path = repository.getClassName() + ".prototype." + method; 101 | adapter.invokeInstanceMethod(path, creationParameters, parameters, 102 | callback); 103 | } 104 | 105 | /** 106 | * Invokes a remotable method exposed within instances of this class on the 107 | * server, 108 | * parses the response as binary data. 109 | * @param method The method to invoke (without the repository), e.g. 110 | * "doSomething". 111 | * @param parameters The parameters to invoke with. 112 | * @param callback The callback to invoke when the execution finishes. 113 | */ 114 | public void invokeMethod(String method, 115 | Map parameters, 116 | Adapter.BinaryCallback callback) { 117 | Adapter adapter = repository.getAdapter(); 118 | if (adapter == null) { 119 | throw new IllegalArgumentException( 120 | "Repository adapter cannot be null"); 121 | } 122 | String path = repository.getClassName() + ".prototype." + method; 123 | adapter.invokeInstanceMethod(path, creationParameters, parameters, 124 | callback); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/remoting/adapters/RestContract.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 StrongLoop. All rights reserved. 2 | 3 | package com.strongloop.android.remoting.adapters; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * A contract specifies how remote method names map to HTTP routes. 10 | * 11 | * For example, if a remote method on the server has been remapped like so: 12 | * 13 | *
 14 |  *{@code
 15 |  * project.getObject = function (id, callback) {
 16 |  *     callback(null, { ... });
 17 |  * };
 18 |  * helper.method(project.getObject, {
 19 |  *     http: { verb: 'GET', path: '/:id'},
 20 |  *     accepts: { name: 'id', type: 'string' }
 21 |  *     returns: { name: 'object', type: 'object' }
 22 |  * })
 23 |  *}
 24 |  *
25 | * 26 | * The new route is GET /:id, instead of POST /project/getObject, so we 27 | * need to update our contract on the client: 28 | * 29 | *
 30 |  *{@code
 31 |  * contract.addItem(new RestContractItem("/:id", "GET"), "project.getObject");
 32 |  *}
 33 |  *
34 | */ 35 | public class RestContract { 36 | 37 | private Map items = 38 | new HashMap(); 39 | 40 | /** 41 | * Adds a single item to this contract. The item can be shared among 42 | * different contracts, managed by the sum of all contracts that contain it. 43 | * Similarly, each item can be used for more than one method, like so: 44 | *
 45 |      * {@code
 46 |      * RestContractItem upsert = new RestContractItem("/widgets/:id", "PUT");
 47 |      * contract.addItem(upsert, "widgets.create");
 48 |      * contract.addItem(upsert, "widgets.update");
 49 |      * }
 50 |      * 
51 | * @param item The item to add to this contract. 52 | * @param method The method the item should represent. 53 | */ 54 | public void addItem(RestContractItem item, String method) { 55 | if (item == null || method == null) { 56 | throw new IllegalArgumentException( 57 | "Neither item nor method can be null"); 58 | } 59 | items.put(method, item); 60 | } 61 | 62 | /** 63 | * Adds all items from contract. 64 | * @see #addItem(RestContractItem, String) 65 | * @param contract The contract to copy from. 66 | */ 67 | public void addItemsFromContract(RestContract contract) { 68 | if (contract == null) { 69 | throw new IllegalArgumentException("Contract cannot be null"); 70 | } 71 | items.putAll(contract.items); 72 | } 73 | 74 | /** 75 | * Returns the custom pattern representing the given method string, or 76 | * null if no custom pattern exists. 77 | * @param method The method to resolve. 78 | * @return The custom pattern if one exists, null otherwise. 79 | */ 80 | public String getPatternForMethod(String method) { 81 | if (method == null) { 82 | throw new IllegalArgumentException("Method cannot be null"); 83 | } 84 | 85 | RestContractItem item = items.get(method); 86 | 87 | return item != null ? item.getPattern() : null; 88 | } 89 | 90 | /** 91 | * Gets the HTTP verb for the given method string. 92 | * @param method The method to resolve. 93 | * @return The resolved verb, or "POST" if it isn't defined. 94 | */ 95 | public String getVerbForMethod(String method) { 96 | if (method == null) { 97 | throw new IllegalArgumentException("Method cannot be null"); 98 | } 99 | 100 | RestContractItem item = items.get(method); 101 | 102 | return item != null ? item.getVerb() : "POST"; 103 | } 104 | 105 | /** 106 | * Gets the ParameterEncoding for the given method. 107 | * 108 | * @param method The method to resolve. 109 | * @return The parameter encoding. 110 | */ 111 | public RestAdapter.ParameterEncoding getParameterEncodingForMethod(String method) { 112 | if (method == null) { 113 | throw new IllegalArgumentException("Method cannot be null"); 114 | } 115 | 116 | RestContractItem item = items.get(method); 117 | 118 | return item != null 119 | ? item.getParameterEncoding() 120 | : RestAdapter.ParameterEncoding.JSON; 121 | } 122 | 123 | /** 124 | * Resolves a specific method, replacing pattern fragments with the optional 125 | * parameters as appropriate. 126 | * @param method The method to resolve. 127 | * @param parameters Pattern parameters. Can be null. 128 | * @return The complete, resolved URL. 129 | */ 130 | public String getUrlForMethod(String method, 131 | Map parameters) { 132 | if (method == null) { 133 | throw new IllegalArgumentException("Method cannot be null"); 134 | } 135 | 136 | String pattern = getPatternForMethod(method); 137 | 138 | if (pattern != null) { 139 | return getUrl(pattern, parameters); 140 | } 141 | else { 142 | return getUrlForMethodWithoutItem(method); 143 | } 144 | } 145 | 146 | /** 147 | * Generates a fallback URL for a method whose contract has not been 148 | * customized. 149 | * @param method The method to generate from. 150 | * @return The resolved URL. 151 | */ 152 | public String getUrlForMethodWithoutItem(String method) { 153 | if (method == null) { 154 | throw new IllegalArgumentException("Method cannot be null"); 155 | } 156 | 157 | return method.replace('.', '/'); 158 | } 159 | 160 | /** 161 | * Returns a rendered URL pattern using the parameters provided. For 162 | * example, the pattern "/widgets/:id" with the parameters 163 | * that contain the value "57" for key "id", 164 | * begets "/widgets/57". 165 | * @param pattern The pattern to render. 166 | * @param parameters The values to render with. 167 | * @return The rendered URL. 168 | */ 169 | public String getUrl(String pattern, 170 | Map parameters) { 171 | if (pattern == null) { 172 | throw new IllegalArgumentException("Pattern cannot be null"); 173 | } 174 | 175 | String url = pattern; 176 | if (parameters == null) { 177 | return url; 178 | } 179 | 180 | for (Map.Entry entry : 181 | parameters.entrySet()) { 182 | String key = ":" + entry.getKey(); 183 | String value = String.valueOf(entry.getValue()); 184 | url = url.replace(key, value); 185 | } 186 | 187 | return url; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/remoting/adapters/RestContractItem.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.remoting.adapters; 2 | 3 | /** 4 | * A single item within a larger SLRESTContract, encapsulation a single route's 5 | * verb and pattern, e.g. GET /widgets/:id. 6 | */ 7 | public class RestContractItem { 8 | 9 | private final String pattern; 10 | private final String verb; 11 | private final RestAdapter.ParameterEncoding parameterEncoding; 12 | 13 | /** 14 | * Creates a new item encapsulating the given pattern and the default verb, 15 | * "POST". 16 | * @param pattern The pattern corresponding to this route, e.g. 17 | * "/widgets/:id". 18 | */ 19 | public RestContractItem(String pattern) { 20 | this(pattern, "POST"); 21 | } 22 | 23 | /** 24 | * Creates a new item encapsulating the given pattern and verb. 25 | * @param pattern The pattern corresponding to this route, e.g. 26 | * "/widgets/:id". 27 | * @param verb The verb corresponding to this route, e.g. 28 | * "GET". 29 | */ 30 | public RestContractItem(String pattern, String verb) { 31 | this(pattern, verb, RestAdapter.ParameterEncoding.JSON); 32 | } 33 | 34 | /** 35 | * Creates a new item encapsulating a route that expects multi-part request 36 | * (e.g. file upload). 37 | * @param pattern The pattern corresponding to this route, e.g. 38 | * "/files/:id". 39 | * @param verb The verb corresponding to this route, e.g. 40 | * "POST". 41 | * @return The RestContractItem created. 42 | */ 43 | public static RestContractItem createMultipart(String pattern, String verb) { 44 | return new RestContractItem(pattern, verb, 45 | RestAdapter.ParameterEncoding.FORM_MULTIPART); 46 | } 47 | 48 | private RestContractItem(String pattern, 49 | String verb, 50 | RestAdapter.ParameterEncoding parameterEncoding) { 51 | this.pattern = pattern; 52 | this.verb = verb; 53 | this.parameterEncoding = parameterEncoding; 54 | } 55 | 56 | /** 57 | * Gets the pattern corresponding to this route, e.g. 58 | * "/widgets/:id". 59 | * @return the pattern. 60 | */ 61 | public String getPattern() { 62 | return pattern; 63 | } 64 | 65 | /** 66 | * Gets the verb corresponding to this route, e.g. "GET". 67 | * @return the verb. 68 | */ 69 | public String getVerb() { 70 | return verb; 71 | } 72 | 73 | /** 74 | * Gets a boolean that indicates if the item is a multipart form mime type. 75 | * @return true if the item is multipart 76 | */ 77 | public RestAdapter.ParameterEncoding getParameterEncoding() { 78 | return parameterEncoding; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/strongloop/android/remoting/adapters/StreamParam.java: -------------------------------------------------------------------------------- 1 | package com.strongloop.android.remoting.adapters; 2 | 3 | import com.loopj.android.http.RequestParams; 4 | 5 | import java.io.InputStream; 6 | 7 | /** 8 | * A request parameter that is a (binary) stream. 9 | */ 10 | public class StreamParam { 11 | private final InputStream stream; 12 | private final String fileName; 13 | private final String contentType; 14 | 15 | public StreamParam(InputStream stream, String fileName) { 16 | this(stream, fileName, null); 17 | } 18 | 19 | public StreamParam(InputStream stream, String fileName, String contentType) { 20 | this.stream = stream; 21 | this.fileName = fileName; 22 | this.contentType = contentType; 23 | } 24 | 25 | public void putTo(RequestParams params, String key) { 26 | params.put(key, stream, fileName, contentType); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test-server/index.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013,2014. All Rights Reserved. 2 | // Node module: loopback-sdk-android 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var path = require('path'); 7 | var SG = require('strong-globalize'); 8 | SG.SetRootDir(__dirname); 9 | var g = SG(); 10 | var async = require('async'); 11 | var loopback = require('loopback'); 12 | 13 | // start strong-remoting's test server 14 | require('./remoting'); 15 | 16 | // setup loopback's test server 17 | var app = loopback(); 18 | app.dataSource('Memory', { 19 | connector: loopback.Memory, 20 | }); 21 | 22 | var lbpn = require('loopback-component-push'); 23 | var PushModel = lbpn.createPushModel(); 24 | app.model(lbpn.Installation, { dataSource: 'Memory' }); 25 | 26 | var Widget = app.model('widget', { 27 | properties: { 28 | name: { 29 | type: String, 30 | required: true 31 | }, 32 | bars: { 33 | type: Number, 34 | required: false 35 | }, 36 | data: { 37 | type: Object, 38 | required: false 39 | } 40 | }, 41 | dataSource: 'Memory' 42 | }); 43 | 44 | Widget.destroyAll(function () { 45 | Widget.create({ 46 | name: 'Foo', 47 | bars: 0, 48 | data: { 49 | quux: true 50 | } 51 | }); 52 | Widget.create({ 53 | name: 'Bar', 54 | bars: 1 55 | }); 56 | }); 57 | 58 | app.model(loopback.AccessToken, { public: false, dataSource: 'Memory' }); 59 | app.model(loopback.ACL, { public: false, dataSource: 'Memory' }); 60 | app.model(loopback.Role, { public: false, dataSource: 'Memory' }); 61 | app.model(loopback.RoleMapping, { public: false, dataSource: 'Memory' }); 62 | 63 | app.model('Customer', { 64 | options: { 65 | base: 'User', 66 | relations: { 67 | accessTokens: { 68 | model: "AccessToken", 69 | type: "hasMany", 70 | foreignKey: "userId" 71 | } 72 | } 73 | }, 74 | dataSource: 'Memory' 75 | }); 76 | 77 | // storage service 78 | var fs = require('fs'); 79 | var storage = path.join(__dirname, 'storage'); 80 | if (!fs.existsSync(storage)) 81 | fs.mkdirSync(storage); 82 | app.dataSource('storage', { 83 | connector: require('loopback-component-storage'), 84 | provider: 'filesystem', 85 | root: storage 86 | }); 87 | 88 | var Container = app.dataSources.storage.createModel('container'); 89 | app.model(Container); 90 | 91 | Container.destroyAll = function(cb) { 92 | Container.getContainers(function(err, containers) { 93 | if (err) return cb(err); 94 | async.each( 95 | containers, 96 | function(item, next) { 97 | Container.destroyContainer(item.name, next); 98 | }, 99 | cb 100 | ); 101 | }); 102 | }; 103 | 104 | Container.destroyAll.shared = true; 105 | Container.destroyAll.http = { verb: 'del', path: '/' } 106 | 107 | app.use(require('morgan')('loopback> :method :url :status')); 108 | app.enableAuth(); 109 | app.use(loopback.rest()); 110 | app.listen(3000, function() { 111 | console.log(g.f('{{LoopBack}} test server listening on {{http://localhost:3000/}}')); 112 | }); 113 | -------------------------------------------------------------------------------- /test-server/remoting/contract-class.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: loopback-sdk-android 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | /** 7 | * A simple class that contains a name, this time with a custom HTTP contract. 8 | */ 9 | function ContractClass(name) { 10 | this.name = name; 11 | } 12 | 13 | /** 14 | * In order to expose ContractClass, we need a "shared constructor". 15 | */ 16 | ContractClass.sharedCtor = function (name, callback) { 17 | callback(null, new ContractClass(name)); 18 | }; 19 | ContractClass.shared = true; 20 | ContractClass.sharedCtor.accepts = [{ arg: 'name', type: 'string' }]; 21 | ContractClass.sharedCtor.http = { path: '/:name' }; 22 | ContractClass.sharedCtor.returns = { type: 'object', root: true }; 23 | 24 | /** 25 | * Returns the ContractClass instance's name. 26 | */ 27 | ContractClass.prototype.getName = function(callback) { 28 | callback(null, this.name); 29 | }; 30 | ContractClass.prototype.getName.shared = true; 31 | ContractClass.prototype.getName.accepts = []; 32 | ContractClass.prototype.getName.returns = [{ arg: 'data', type: 'string' }]; 33 | 34 | /** 35 | * Takes in a name, returning a greeting for that name. 36 | */ 37 | ContractClass.prototype.greet = function(other, callback) { 38 | callback(null, 'Hi, ' + other + '!'); 39 | }; 40 | ContractClass.prototype.greet.shared = true; 41 | ContractClass.prototype.greet.accepts = [{ arg: 'other', type: 'string' }]; 42 | ContractClass.prototype.greet.returns = [{ arg: 'data', type: 'string' }]; 43 | 44 | /** 45 | * Returns the ContractClass prototype's favorite person's name. 46 | */ 47 | ContractClass.getFavoritePerson = function(callback) { 48 | callback(null, 'You'); 49 | }; 50 | ContractClass.getFavoritePerson.shared = true; 51 | ContractClass.getFavoritePerson.accepts = []; 52 | ContractClass.getFavoritePerson.returns = [{ arg: 'data', type: 'string' }]; 53 | 54 | module.exports = ContractClass; 55 | -------------------------------------------------------------------------------- /test-server/remoting/contract.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: loopback-sdk-android 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | /** 7 | * Returns a secret message. 8 | */ 9 | function getSecret(callback) { 10 | callback(null, 'shhh!'); 11 | } 12 | getSecret.shared = true; 13 | getSecret.accepts = []; 14 | getSecret.returns = [{ arg: 'data', type: 'string' }]; 15 | getSecret.http = { path: '/customizedGetSecret', verb: 'GET' }; 16 | 17 | /** 18 | * Takes a string and returns an updated string. 19 | */ 20 | function transform(str, callback) { 21 | callback(null, 'transformed: ' + str); 22 | } 23 | transform.shared = true; 24 | transform.accepts = [{ arg: 'str', type: 'string' }]; 25 | transform.returns = [{ arg: 'data', type: 'string' }]; 26 | transform.http = { path: '/customizedTransform', verb: 'GET' }; 27 | 28 | /** 29 | * Takes a GeoPoint and returns back latitude and longitude 30 | */ 31 | function geopoint(here, callback) { 32 | callback(null, here.lat, here.lng); 33 | } 34 | 35 | geopoint.shared = true; 36 | geopoint.accepts = [ {arg: 'here', type: 'GeoPoint', required: true }]; 37 | geopoint.returns = [ 38 | {arg: 'lat', type: 'number'}, 39 | {arg: 'lng', type: 'number'} 40 | ]; 41 | geopoint.http = { path: '/geopoint', verb: 'GET' }; 42 | 43 | /** 44 | * A stub for a listing function accepting a filter like 45 | * ?filter=where[effectiveRange][gt]=900 46 | * This method returns back the filter object as JSON. 47 | * { data: "{\"where\":{\"age\":{\"gt\":21}}}" } 48 | */ 49 | function list(filter, callback) { 50 | callback(null, JSON.stringify(filter)); 51 | } 52 | 53 | list.shared = true; 54 | list.accepts = [{ arg: 'filter', type: 'object', require: true }]; 55 | list.returns = [{ arg: 'data', type: 'string' }]; 56 | list.http = { path: '/list', verb: 'GET' }; 57 | 58 | function getAuthorizationHeader(auth, cb) { 59 | cb(null, auth); 60 | } 61 | 62 | getAuthorizationHeader.shared = true; 63 | getAuthorizationHeader.accepts = [{ arg: 'auth', type: 'string', http: function(ctx) { 64 | return ctx.req.header('authorization'); 65 | }}]; 66 | getAuthorizationHeader.returns = [{ arg: 'data', type: 'string' }]; 67 | getAuthorizationHeader.http = { path: '/get-auth' }; 68 | 69 | function binary(res) { 70 | res.type('application/octet-stream'); 71 | res.send(200, new Buffer('010203', 'hex')); 72 | } 73 | binary.shared = true; 74 | binary.accepts = [{arg: 'res', type: 'object', 'http': {source: 'res'}}]; 75 | 76 | module.exports = { 77 | getSecret: getSecret, 78 | transform: transform, 79 | geopoint: geopoint, 80 | getAuthorizationHeader: getAuthorizationHeader, 81 | binary: binary, 82 | list: list 83 | }; 84 | -------------------------------------------------------------------------------- /test-server/remoting/index.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: loopback-sdk-android 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var SG = require('strong-globalize'); 7 | var g = SG(); 8 | var express = require('express'); 9 | var remotes = require('strong-remoting').create(); 10 | var SharedClass = require('strong-remoting').SharedClass; 11 | 12 | remotes.exports = { 13 | simple: require('./simple'), 14 | contract: require('./contract'), 15 | }; 16 | 17 | remotes.addClass(new SharedClass('SimpleClass', require('./simple-class'))); 18 | remotes.addClass(new SharedClass('ContractClass', require('./contract-class'))); 19 | 20 | var app = express(); 21 | app.use(require('morgan')('strong-remoting> :method :url :status')); 22 | app.use(remotes.handler('rest')); 23 | 24 | var server = require('http') 25 | .createServer(app) 26 | .listen(3001, function() { 27 | console.log(g.f( 28 | '{{strong-remoting}} test server listening on {{http://localhost:3001/}}')); 29 | }); 30 | -------------------------------------------------------------------------------- /test-server/remoting/simple-class.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: loopback-sdk-android 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | /** 7 | * A simple class that contains a name. 8 | */ 9 | function SimpleClass(name) { 10 | this.name = name; 11 | } 12 | 13 | /** 14 | * In order to expose SimpleClass, we need a "shared constructor". 15 | */ 16 | SimpleClass.sharedCtor = function (name, callback) { 17 | callback(null, new SimpleClass(name)); 18 | }; 19 | SimpleClass.shared = true; 20 | SimpleClass.sharedCtor.accepts = [{ arg: 'name', type: 'string' }]; 21 | SimpleClass.sharedCtor.returns = { type: 'object', root: true }; 22 | SimpleClass.sharedCtor.http = { path: '/:id' }; 23 | 24 | /** 25 | * Returns the SimpleClass instance's name. 26 | */ 27 | SimpleClass.prototype.getName = function(callback) { 28 | callback(null, this.name); 29 | }; 30 | SimpleClass.prototype.getName.shared = true; 31 | SimpleClass.prototype.getName.accepts = []; 32 | SimpleClass.prototype.getName.returns = [{ arg: 'data', type: 'string' }]; 33 | 34 | /** 35 | * Takes in a name, returning a greeting for that name. 36 | */ 37 | SimpleClass.prototype.greet = function(other, callback) { 38 | callback(null, 'Hi, ' + other + '!'); 39 | }; 40 | SimpleClass.prototype.greet.shared = true; 41 | SimpleClass.prototype.greet.accepts = [{ arg: 'other', type: 'string' }]; 42 | SimpleClass.prototype.greet.returns = [{ arg: 'data', type: 'string' }]; 43 | 44 | /** 45 | * Returns the SimpleClass prototype's favorite person's name. 46 | */ 47 | SimpleClass.getFavoritePerson = function(callback) { 48 | callback(null, 'You'); 49 | }; 50 | SimpleClass.getFavoritePerson.shared = true; 51 | SimpleClass.getFavoritePerson.accepts = []; 52 | SimpleClass.getFavoritePerson.returns = [{ arg: 'data', type: 'string' }]; 53 | 54 | module.exports = SimpleClass; 55 | -------------------------------------------------------------------------------- /test-server/remoting/simple.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: loopback-sdk-android 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | /** 7 | * Returns a secret message. 8 | */ 9 | function getSecret(callback) { 10 | callback(null, 'shhh!'); 11 | } 12 | getSecret.shared = true; 13 | getSecret.accepts = []; 14 | getSecret.returns = [{ arg: 'data', type: 'string' }]; 15 | 16 | /** 17 | * Takes a string and returns an updated string. 18 | */ 19 | function transform(str, callback) { 20 | callback(null, 'transformed: ' + str); 21 | } 22 | transform.shared = true; 23 | transform.accepts = [{ arg: 'str', type: 'string' }]; 24 | transform.returns = [{ arg: 'data', type: 'string' }]; 25 | 26 | module.exports = { 27 | getSecret: getSecret, 28 | transform: transform 29 | }; 30 | --------------------------------------------------------------------------------