├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE.md ├── README.md ├── astra.json ├── mvnw ├── mvnw.cmd ├── pom.xml └── src └── main ├── java └── com │ └── datastax │ └── astra │ ├── GettingStartedWithAstra.java │ ├── controller │ ├── ControllerExceptionHandler.java │ ├── CredentialsController.java │ ├── InstrumentsController.java │ └── SpacecraftController.java │ ├── dao │ ├── SessionManager.java │ ├── SpacecraftInstrumentsDao.java │ ├── SpacecraftInstrumentsQueryProvider.java │ ├── SpacecraftJourneyDao.java │ └── SpacecraftMapper.java │ ├── doc │ ├── DocumentationApiConfiguration.java │ └── DocumentationController.java │ ├── entity │ ├── AbstractInstrumentReading.java │ ├── LocationUdt.java │ ├── SpacecraftJourneyCatalog.java │ ├── SpacecraftLocationOverTime.java │ ├── SpacecraftPressureOverTime.java │ ├── SpacecraftSpeedOverTime.java │ └── SpacecraftTemperatureOverTime.java │ ├── model │ └── PagedResultWrapper.java │ ├── service │ └── AstraService.java │ └── utils │ └── CqlFileUtils.java └── resources ├── application.conf ├── application.yml ├── banner.txt ├── logback.xml └── schema.cql /.gitignore: -------------------------------------------------------------------------------- 1 | # eclipse conf file 2 | .settings 3 | .classpath 4 | .project 5 | .cache 6 | 7 | # idea conf files 8 | .idea 9 | *.ipr 10 | *.iws 11 | *.iml 12 | 13 | # building 14 | target 15 | build 16 | tmp 17 | dist 18 | 19 | # misc 20 | .DS_Store 21 | 22 | .factorypath 23 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at . All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a [Code of Conduct](CODE_OF_CONDUCT.md), please follow it in all your interactions with the project. 7 | 8 | ## Found an Issue? 9 | If you find a bug in the source code or a mistake in the documentation, you can help us by 10 | [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can 11 | [submit a Pull Request](#submit-pr) with a fix. 12 | 13 | ## Want a Feature? 14 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub 15 | Repository. If you would like to *implement* a new feature, please submit an issue with 16 | a proposal for your work first, to be sure that we can use it. 17 | 18 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 19 | 20 | ## Contribution Guidelines 21 | 22 | ### Submitting an Issue 23 | Before you submit an issue, search the archive, maybe your question was already answered. 24 | 25 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 26 | Help us to maximize the effort we can spend fixing issues and adding new 27 | features, by not reporting duplicate issues. Providing the following information will increase the 28 | chances of your issue being dealt with quickly: 29 | 30 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps 31 | * **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you 32 | * **Reproduce the Error** - provide a live example or a unambiguous set of steps 33 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 34 | causing the problem (line of code or commit) 35 | 36 | ### Submitting a Pull Request (PR) 37 | Before you submit your Pull Request (PR) consider the following guidelines: 38 | 39 | * Search the repository (https://github.com/bechbd/[repository-name]/pulls) for an open or closed PR that relates to your submission. You don't want to duplicate effort. 40 | 41 | * Create a fork of the repo 42 | * Navigate to the repo you want to fork 43 | * In the top right corner of the page click **Fork**: 44 | ![](https://help.github.com/assets/images/help/repository/fork_button.jpg) 45 | 46 | * Make your changes in the forked repo 47 | * Commit your changes using a descriptive commit message 48 | * In GitHub, create a pull request: https://help.github.com/en/articles/creating-a-pull-request-from-a-fork 49 | * If we suggest changes then: 50 | * Make the required updates. 51 | * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): 52 | 53 | ```shell 54 | git rebase master -i 55 | git push -f 56 | ``` 57 | 58 | That's it! Thank you for your contribution! -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11 2 | 3 | MAINTAINER Cedrick Lunven 4 | 5 | ######################################################## 6 | ## Environment Variables 7 | ######################################################## 8 | 9 | VOLUME /tmp 10 | ARG JAR_FILE 11 | ADD ${JAR_FILE} app.jar 12 | ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] 13 | 14 | # Exposing expected port by WEBUI 15 | EXPOSE 8080 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Getting Started with Apache Cassandra™ and Java using DataStax Astra DB 3 | *50 minutes, Intermediate, [Start Building](https://github.com/DataStax-Examples/getting-started-with-astra-java#prerequisites)* 4 | 5 | This provides an example REST backend built in Java using `Spring Boot` for use with the [Getting Started with Astra UI](https://github.com/DataStax-Examples/getting-started-with-astra-ui). 6 | 7 | 8 | 9 | ![image](https://raw.githubusercontent.com/DataStax-Examples/sample-app-template/master/screenshots/astra-sample-app-default.png) 10 | 11 | 12 | ## Objectives 13 | * How to connect to Astra DB via the Secure Connect Bundle 14 | * How to manage a Cassandra Session within a JAVA web application 15 | 16 | ## How this Works 17 | This is an example of a Spring Boot Microservice for use with the Astra Getting Started UI which is found [here](https://github.com/DataStax-Examples/getting-started-with-astra-ui). 18 | 19 | ## Get Started 20 | To build and play with this app, follow the build instructions that are located here: [https://github.com/DataStax-Examples/getting-started-with-astra-java](https://github.com/DataStax-Examples/getting-started-with-astra-java#prerequisites) 21 | 22 | 23 | ## Prerequisites 24 | Let's do some initial setup by creating a serverless(!) database. 25 | 26 | ### DataStax Astra 27 | 1. Create a [DataStax Astra account](https://dtsx.io/3zBltF7) if you don't already have one: 28 | ![image](https://raw.githubusercontent.com/DataStax-Examples/sample-app-template/master/screenshots/astra-register-basic-auth.png) 29 | 30 | 2. On the home page. Locate the button **`Create Database`** 31 | ![image](https://raw.githubusercontent.com/DataStax-Examples/sample-app-template/master/screenshots/astra-dashboard.png) 32 | 33 | 3. Locate the **`Get Started`** button to continue 34 | ![image](https://raw.githubusercontent.com/DataStax-Examples/sample-app-template/master/screenshots/astra-select-plan.png) 35 | 36 | 4. Define a **database name**, **keyspace name** and select a database **region**, then click **create database**. 37 | ![image](https://raw.githubusercontent.com/DataStax-Examples/sample-app-template/master/screenshots/astra-create-db.png) 38 | 39 | 5. Your Astra DB will be ready when the status will change from *`Pending`* to **`Active`** 💥💥💥 40 | ![image](https://raw.githubusercontent.com/DataStax-Examples/sample-app-template/master/screenshots/astra-db-active.png) 41 | 42 | 6. After your database is provisioned, we need to generate an Application Token for our App. Go to the `Settings` tab in the database home screen. 43 | ![image](https://raw.githubusercontent.com/DataStax-Examples/sample-app-template/master/screenshots/astra-db-settings.png) 44 | 45 | 7. Select `Admin User` for the role for this Sample App and then generate the token. Download the CSV so that we can use the credentials we need later. 46 | ![image](https://raw.githubusercontent.com/DataStax-Examples/sample-app-template/master/screenshots/astra-db-settings-token.png) 47 | 48 | 8. After you have your Application Token, head to the database connect screen and select the driver connection that we need. Go ahead and download the `Secure Bundle` for the driver. 49 | ![image](https://raw.githubusercontent.com/DataStax-Examples/sample-app-template/master/screenshots/astra-db-connect-bundle.png) 50 | 51 | 9. Make note of where to use the `Client Id` and `Client Secret` that is part of the Application Token that we generated earlier. 52 | ![image](https://raw.githubusercontent.com/DataStax-Examples/sample-app-template/master/screenshots/astra-db-connect-bundle-driver.png) 53 | 54 | ### Github 55 | 1. Click `Use this template` at the top of the [GitHub Repository](https://github.com/DataStax-Examples/getting-started-with-astra-java): 56 | ![image](https://raw.githubusercontent.com/DataStax-Examples/sample-app-template/master/screenshots/github-use-template.png) 57 | 58 | 2. Enter a repository name and click 'Create repository from template': 59 | ![image](https://raw.githubusercontent.com/DataStax-Examples/sample-app-template/master/screenshots/github-create-repository.png) 60 | 61 | 3. Clone the repository: 62 | ![image](https://raw.githubusercontent.com/DataStax-Examples/sample-app-template/master/screenshots/github-clone.png) 63 | 64 | ## 🚀 Getting Started Paths: 65 | *Make sure you've completed the [prerequisites](#prerequisites) before starting this step* 66 | - [Running on your local machine](#running-on-your-local-machine) 67 | 68 | ### Running on your local machine 69 | Make sure that you have: 70 | * Java 11 71 | * An Astra DB compatible Java driver, instructions may be found [here](https://docs.datastax.com/en/astra/docs/connect/drivers/connect-java.html) to install this locally. 72 | * An Astra database with the CQL schema located in [schema.cql](https://raw.githubusercontent.com/DataStax-Examples/getting-started-with-astra-java/master/src/main/resources/schema.cql) already added. 73 | * The username, password, keyspace name, and secure connect bundle downloaded from your Astra DB. For information on how to obtain these credentials please read the documentation found [here](https://docs.datastax.com/en/astra/docs/connect/secure-connect-bundle.html) 74 | 75 | This application is a Spring Boot web application. This sample can be run from the root directory using: 76 | ```sh 77 | cd getting-started-with-astra-java 78 | 79 | mvn spring-boot:run 80 | ``` 81 | 82 | This will startup the application running on `http://localhost:8080` 83 | 84 | You will know that you are up and working when you get the following in your terminal window: 85 | ```sh 86 | 16:23:01.569 INFO com.datastax.astra.GettingStartedWithAstra : Started GettingStartedWithAstra in 1.851 seconds (JVM running for 2.39) 87 | ``` 88 | 89 | #### Access the API documentation from a browser 90 | 91 | [http://localhost:8080](http://localhost:8080) 92 | 93 | *Note: If you want to change the listening port of the application, locate the file `src/main/resources/application.yml` and change key `server.port`* 94 | 95 | #### Setup the user interface to use this backend 96 | To setup the UI to connect to Java backend define a `.env` file in the `getting-started-with-astra-ui` project main directory. Inside the file it should have one entry pointing to this project's API endpoint: 97 | ```sh 98 | BASE_ADDRESS=http://localhost:8080/api 99 | ``` 100 | Once you start that project with a `npm run build` it will point the UI to the backend API which will then be using Astra DB as a database. When you first connect to the UI, a dialog box will open asking for Astra DB connection information. 101 | 102 | -------------------------------------------------------------------------------- /astra.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Getting Started with Java + Spring + Astra DB", 3 | "description": "This provides an example REST backend built in Java using Spring Boot for use with the Getting Started with Astra UI.", 4 | "duration": "50 minutes", 5 | "skillLevel": "Intermediate", 6 | "githubUrl": "https://github.com/DataStax-Examples/getting-started-with-astra-java", 7 | "tags": [ 8 | { "name": "labs", "color": "warning" }, 9 | { "name": "java" }, 10 | { "name": "spring" }, 11 | { "name": "java driver" } 12 | ], 13 | "category": "apps", 14 | "priority": 3, 15 | "heroImage": "https://raw.githubusercontent.com/DataStax-Examples/sample-app-template/master/screenshots/astra-sample-app-default.png" 16 | } 17 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.datastax.astra 8 | getting-started-with-astra-java 9 | 1.0.0-SNAPSHOT 10 | getting-started-with-astra-java 11 | Sample Application to work with Astra 12 | jar 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.1.9.RELEASE 18 | 19 | 20 | 21 | 22 | 8 23 | 4.6.0 24 | 2.9.2 25 | 3.8.1 26 | 1.4.12 27 | UTF-8 28 | 29 | 30 | 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-web 36 | 37 | 38 | 39 | 40 | io.springfox 41 | springfox-swagger2 42 | ${swagger.version} 43 | 44 | 45 | io.springfox 46 | springfox-swagger-ui 47 | ${swagger.version} 48 | 49 | 50 | 51 | 52 | com.datastax.oss 53 | java-driver-core 54 | ${java-driver.version} 55 | 56 | 57 | com.datastax.oss 58 | java-driver-query-builder 59 | ${java-driver.version} 60 | 61 | 62 | com.datastax.oss 63 | java-driver-mapper-runtime 64 | ${java-driver.version} 65 | 66 | 67 | 68 | 69 | org.apache.commons 70 | commons-lang3 71 | 72 | 73 | com.fasterxml.jackson.datatype 74 | jackson-datatype-jsr310 75 | 76 | 77 | commons-fileupload 78 | commons-fileupload 79 | 1.3.3 80 | 81 | 82 | org.apache.commons 83 | commons-io 84 | 1.3.2 85 | 86 | 87 | 88 | 89 | org.springframework.boot 90 | spring-boot-configuration-processor 91 | true 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | org.springframework.boot 101 | spring-boot-maven-plugin 102 | 103 | 104 | 105 | com.spotify 106 | dockerfile-maven-plugin 107 | ${version.maven.plugin.docker} 108 | 109 | clunven/getting-started-with-astra-java 110 | 111 | target/${project.build.finalName}.jar 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | org.apache.maven.plugins 121 | maven-compiler-plugin 122 | ${version.maven.plugin.compiler} 123 | 124 | 125 | 126 | com.datastax.oss 127 | java-driver-mapper-processor 128 | ${java-driver.version} 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | datastax-releases-public-local 140 | https://repo.datastax.com/datastax-public-releases-local/ 141 | 142 | true 143 | 144 | 145 | false 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/GettingStartedWithAstra.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Jeff Carpenter 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.datastax.astra; 17 | 18 | import org.springframework.boot.SpringApplication; 19 | import org.springframework.boot.autoconfigure.SpringBootApplication; 20 | 21 | @SpringBootApplication 22 | public class GettingStartedWithAstra { 23 | 24 | public static void main(String[] args) { 25 | SpringApplication.run(GettingStartedWithAstra.class, args); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/controller/ControllerExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.controller; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.web.bind.annotation.ControllerAdvice; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | import org.springframework.web.bind.annotation.ResponseStatus; 9 | 10 | @ControllerAdvice 11 | public class ControllerExceptionHandler { 12 | 13 | /** Logger for the class. */ 14 | private static final Logger LOGGER = LoggerFactory.getLogger(ControllerExceptionHandler.class); 15 | 16 | @ExceptionHandler(value = IllegalArgumentException.class) 17 | @ResponseStatus(value = HttpStatus.BAD_REQUEST) 18 | public String handleBadRequest(IllegalArgumentException ex) { 19 | LOGGER.error("Illegal Argument : {}", ex.getMessage()); 20 | return ex.getMessage(); 21 | } 22 | 23 | @ExceptionHandler(value = IllegalStateException.class) 24 | @ResponseStatus(value = HttpStatus.UNAUTHORIZED) 25 | public String handleUnAuthorized(IllegalStateException ex) { 26 | LOGGER.error("Illegal State : {}", ex.getMessage()); 27 | return ex.getMessage(); 28 | } 29 | 30 | @ExceptionHandler(value = RuntimeException.class) 31 | @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) 32 | public String handleDefaultError(RuntimeException ex) { 33 | LOGGER.error("Default Error : {}", ex.getMessage()); 34 | return ex.getMessage(); 35 | } 36 | 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/controller/CredentialsController.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.controller; 2 | 3 | import static org.springframework.web.bind.annotation.RequestMethod.GET; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.nio.file.Files; 8 | import java.nio.file.StandardCopyOption; 9 | import java.util.UUID; 10 | 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.http.MediaType; 14 | import org.springframework.http.ResponseEntity; 15 | import org.springframework.web.bind.annotation.CrossOrigin; 16 | import org.springframework.web.bind.annotation.PostMapping; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RequestParam; 19 | import org.springframework.web.bind.annotation.RestController; 20 | import org.springframework.web.multipart.MultipartFile; 21 | 22 | import com.datastax.astra.dao.SessionManager; 23 | 24 | import io.swagger.annotations.Api; 25 | import io.swagger.annotations.ApiImplicitParam; 26 | import io.swagger.annotations.ApiImplicitParams; 27 | import io.swagger.annotations.ApiOperation; 28 | import io.swagger.annotations.ApiParam; 29 | import io.swagger.annotations.ApiResponse; 30 | import io.swagger.annotations.ApiResponses; 31 | @CrossOrigin 32 | @RestController 33 | @Api( 34 | value = "/api/credentials", 35 | description = "Send connectivity parameters to initialize component") 36 | @RequestMapping("/api/credentials") 37 | public class CredentialsController { 38 | 39 | /** Logger for the class. */ 40 | private static final Logger LOGGER = LoggerFactory.getLogger(CredentialsController.class); 41 | 42 | /** 43 | * POST on /api/credentials 44 | * 45 | * Request body is the zipfile 46 | * 47 | * @param username 48 | * HTTP POST PARAM user name 49 | * @param password 50 | * HTTP POST PARAM password 51 | * @param keyspace 52 | * name of keysapce 53 | * @return 54 | * status of connection 55 | * @throws IOException 56 | * cannot process incoming file 57 | */ 58 | @PostMapping 59 | @ApiOperation(value = "Save credentials and initiate connection", response = String.class) 60 | @ApiResponses({ 61 | @ApiResponse(code = 200, message = "Credentials are saved and system is now connected"), 62 | @ApiResponse(code = 401, message = "Invalid Credentials"), 63 | @ApiResponse(code = 400, message = "Invalid or missing parameters"), 64 | @ApiResponse(code = 500, message = "Internal error - cannot save file") 65 | }) 66 | @ApiImplicitParams({ 67 | @ApiImplicitParam( 68 | name = "file", 69 | value = "A binary zip file provided by astra to initiate a 2-ways SSL connection", 70 | required = true, dataType = "file", paramType = "form") 71 | }) 72 | public ResponseEntity saveCredentials( 73 | @RequestParam("username") 74 | @ApiParam(name="username", value="login for user authentication", required=true) 75 | String username, 76 | @RequestParam("password") 77 | @ApiParam(name="password", value="password for user authentication", required=true) 78 | String password, 79 | @RequestParam("keyspace") 80 | @ApiParam(name="keyspace", value="keyspace to use", required=true) 81 | String keyspace, 82 | @RequestParam("file") MultipartFile file) throws IOException { 83 | LOGGER.info("Initializing credentials and connection"); 84 | LOGGER.info("+ Zip File found with {} bytes", file.getSize()); 85 | 86 | // Save File Locally in temp folder with generated UID 87 | File tempFile = File.createTempFile(UUID.randomUUID().toString(), ".zip"); 88 | Files.copy(file.getInputStream(), tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING); 89 | LOGGER.info("+ Creating temporary file {}", tempFile.getAbsolutePath()); 90 | 91 | SessionManager.getInstance() 92 | .saveCredentials(username, password, keyspace, tempFile.getAbsolutePath()); 93 | LOGGER.info("+ Saving credentials into SessionManager"); 94 | 95 | // Checking connection 96 | return checkConnection(); 97 | } 98 | 99 | /** 100 | * Check if system is initialized and connected. 101 | */ 102 | @RequestMapping(method = GET) 103 | @ApiOperation(value = "Status for component", response = String.class) 104 | @ApiResponses({ 105 | @ApiResponse(code = 200, message = "System is connected"), 106 | @ApiResponse(code = 401, message = "Invalid Credentials or not initialized") 107 | }) 108 | public ResponseEntity checkConnection() { 109 | SessionManager.getInstance().checkConnection(); 110 | LOGGER.info("Session is successfully initialized and connected"); 111 | return ResponseEntity.ok("Connection Successful"); 112 | } 113 | 114 | @PostMapping(value = "/test", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) 115 | public ResponseEntity testCredentials( 116 | @RequestParam("username") String username, 117 | @RequestParam("password") String password, 118 | @RequestParam("keyspace") String keyspace, 119 | @RequestParam("file") MultipartFile file) throws IOException { 120 | 121 | File tempFile = File.createTempFile(UUID.randomUUID().toString(), ".zip"); 122 | Files.copy(file.getInputStream(), tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING); 123 | LOGGER.info("+ Creating temporary file {}", tempFile.getAbsolutePath()); 124 | 125 | SessionManager.getInstance().testCredentials(username, password, keyspace, tempFile.getAbsolutePath()); 126 | LOGGER.info("Session has been successfully established"); 127 | 128 | return ResponseEntity.ok("Valid Parameters"); 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/controller/InstrumentsController.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.controller; 2 | 3 | import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; 4 | 5 | import java.util.List; 6 | import java.util.Optional; 7 | import java.util.UUID; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.bind.annotation.CrossOrigin; 13 | import org.springframework.web.bind.annotation.GetMapping; 14 | import org.springframework.web.bind.annotation.PathVariable; 15 | import org.springframework.web.bind.annotation.PostMapping; 16 | import org.springframework.web.bind.annotation.RequestBody; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RequestParam; 19 | import org.springframework.web.bind.annotation.RestController; 20 | 21 | import com.datastax.astra.entity.SpacecraftLocationOverTime; 22 | import com.datastax.astra.entity.SpacecraftPressureOverTime; 23 | import com.datastax.astra.entity.SpacecraftSpeedOverTime; 24 | import com.datastax.astra.entity.SpacecraftTemperatureOverTime; 25 | import com.datastax.astra.model.PagedResultWrapper; 26 | import com.datastax.astra.service.AstraService; 27 | 28 | import io.swagger.annotations.Api; 29 | import io.swagger.annotations.ApiOperation; 30 | import io.swagger.annotations.ApiParam; 31 | import io.swagger.annotations.ApiResponse; 32 | 33 | @CrossOrigin 34 | @RestController 35 | @Api( 36 | value = "api/spacecraft/{spacecraftName}/{journeyId}/instruments", 37 | description = "Works with Instruments") 38 | @RequestMapping("api/spacecraft/{spacecraftName}/{journeyId}/instruments") 39 | public class InstrumentsController { 40 | 41 | /** Logger for the class. */ 42 | private static final Logger LOGGER = LoggerFactory.getLogger(InstrumentsController.class); 43 | 44 | /** Service implementation Injection. */ 45 | private AstraService astraService; 46 | 47 | /** 48 | * Constructor. 49 | * 50 | * @param spacecraftService 51 | * service implementation 52 | */ 53 | public InstrumentsController(AstraService astraService) { 54 | this.astraService = astraService; 55 | } 56 | 57 | /** 58 | * Retrieve temperature metrics 59 | */ 60 | @GetMapping(value="/temperature", produces = APPLICATION_JSON_VALUE) 61 | @ApiOperation(value = "Retrieve temperature reading for a journey", response = List.class) 62 | @ApiResponse(code = 200, message = "Retrieve temperature reading for a journey") 63 | public ResponseEntity> getTemperatureReading( 64 | @ApiParam(name="spacecraftName", value="Spacecraft name",example = "gemini3",required=true ) 65 | @PathVariable(value = "spacecraftName") String spacecraftName, 66 | @ApiParam(name="journeyId", value="Identifer for journey",example = "abb7c000-c310-11ac-8080-808080808080",required=true ) 67 | @PathVariable(value = "journeyId") UUID journeyId, 68 | @ApiParam(name="pagesize", value="Requested page size, default is 10", required=false ) 69 | @RequestParam("pagesize") Optional pageSize, 70 | @ApiParam(name="pagestate", value="Use to retrieve next pages", required=false ) 71 | @RequestParam("pagestate") Optional pageState) { 72 | LOGGER.debug("Retrieving temperature readings for spacecraft {} and journey {}", spacecraftName, journeyId); 73 | PagedResultWrapper res = astraService.getTemperatureReading(spacecraftName, journeyId, pageSize, pageState); 74 | return ResponseEntity.ok(res); 75 | } 76 | 77 | /** 78 | * Retrieve pressure metrics 79 | */ 80 | @GetMapping(value="/pressure", produces = APPLICATION_JSON_VALUE) 81 | @ApiOperation(value = "Retrieve pressure reading for a journey", response = List.class) 82 | @ApiResponse(code = 200, message = "Retrieve pressure reading for a journey") 83 | public ResponseEntity> getPressureReading( 84 | @ApiParam(name="spacecraftName", value="Spacecraft name",example = "gemini3",required=true ) 85 | @PathVariable(value = "spacecraftName") String spacecraftName, 86 | @ApiParam(name="journeyId", value="Identifer for journey",example = "abb7c000-c310-11ac-8080-808080808080",required=true ) 87 | @PathVariable(value = "journeyId") UUID journeyId, 88 | @ApiParam(name="pagesize", value="Requested page size, default is 10", required=false ) 89 | @RequestParam("pagesize") Optional pageSize, 90 | @ApiParam(name="pagestate", value="Use to retrieve next pages", required=false ) 91 | @RequestParam("pagestate") Optional pageState) { 92 | LOGGER.debug("Retrieving pressure readings for spacecraft {} and journey {}", spacecraftName, journeyId); 93 | return ResponseEntity.ok(astraService.getPressureReading(spacecraftName, journeyId, pageSize, pageState)); 94 | } 95 | 96 | /** 97 | * Retrieve speed metrics 98 | */ 99 | @GetMapping(value="/speed", produces = APPLICATION_JSON_VALUE) 100 | @ApiOperation(value = "Retrieve speed reading for a journey", response = List.class) 101 | @ApiResponse(code = 200, message = "Retrieve speed reading for a journey") 102 | public ResponseEntity> getSpeedReading( 103 | @ApiParam(name="spacecraftName", value="Spacecraft name",example = "gemini3",required=true ) 104 | @PathVariable(value = "spacecraftName") String spacecraftName, 105 | @ApiParam(name="journeyId", value="Identifer for journey",example = "abb7c000-c310-11ac-8080-808080808080",required=true ) 106 | @PathVariable(value = "journeyId") UUID journeyId, 107 | @ApiParam(name="pagesize", value="Requested page size, default is 10", required=false ) 108 | @RequestParam("pagesize") Optional pageSize, 109 | @ApiParam(name="pagestate", value="Use to retrieve next pages", required=false ) 110 | @RequestParam("pagestate") Optional pageState) { 111 | LOGGER.debug("Retrieving pressure readings for spacecraft {} and journey {}", spacecraftName, journeyId); 112 | return ResponseEntity.ok(astraService.getSpeedReading(spacecraftName, journeyId, pageSize, pageState)); 113 | } 114 | 115 | /** 116 | * Retrieve location metrics 117 | */ 118 | @GetMapping(value="/location", produces = APPLICATION_JSON_VALUE) 119 | @ApiOperation(value = "Retrieve location reading for a journey", response = List.class) 120 | @ApiResponse(code = 200, message = "Retrieve locartion reading for a journey") 121 | public ResponseEntity> getLocationReading( 122 | @ApiParam(name="spacecraftName", value="Spacecraft name",example = "gemini3",required=true ) 123 | @PathVariable(value = "spacecraftName") String spacecraftName, 124 | @ApiParam(name="journeyId", value="Identifer for journey",example = "abb7c000-c310-11ac-8080-808080808080",required=true ) 125 | @PathVariable(value = "journeyId") UUID journeyId, 126 | @ApiParam(name="pagesize", value="Requested page size, default is 10", required=false ) 127 | @RequestParam("pagesize") Optional pageSize, 128 | @ApiParam(name="pagestate", value="Use to retrieve next pages", required=false ) 129 | @RequestParam("pagestate") Optional pageState) { 130 | LOGGER.debug("Retrieving pressure readings for spacecraft {} and journey {}", spacecraftName, journeyId); 131 | return ResponseEntity.ok(astraService.getLocationReading(spacecraftName, journeyId, pageSize, pageState)); 132 | } 133 | 134 | @PostMapping(value="/temperature", consumes = APPLICATION_JSON_VALUE) 135 | @ApiOperation(value = "Save temperature reading for a journey", response = List.class) 136 | @ApiResponse(code = 200, message = "Saved temperature reading for a journey") 137 | public ResponseEntity saveTemperatureReadings( 138 | @ApiParam(name="spacecraftName", value="Spacecraft name",example = "gemini3",required=true ) 139 | @PathVariable(value = "spacecraftName") String spacecraftName, 140 | @ApiParam(name="journeyId", value="Identifier for journey",example = "abb7c000-c310-11ac-8080-808080808080",required=true ) 141 | @PathVariable(value = "journeyId") UUID journeyId, 142 | @RequestBody SpacecraftTemperatureOverTime[] readings) { 143 | LOGGER.debug("Saving temperature readings for spacecraft {} and journey {}", spacecraftName, journeyId); 144 | if (null != readings && readings.length > 0) { 145 | astraService.insertTemperatureReading(readings); 146 | } 147 | return ResponseEntity.ok("OK"); 148 | } 149 | 150 | @PostMapping(value="/location", consumes = APPLICATION_JSON_VALUE) 151 | @ApiOperation(value = "Save location reading for a journey", response = List.class) 152 | @ApiResponse(code = 200, message = "Saved location reading for a journey") 153 | public ResponseEntity saveLocationReadings( 154 | @ApiParam(name="spacecraftName", value="Spacecraft name",example = "gemini3",required=true ) 155 | @PathVariable(value = "spacecraftName") String spacecraftName, 156 | @ApiParam(name="journeyId", value="Identifier for journey",example = "abb7c000-c310-11ac-8080-808080808080",required=true ) 157 | @PathVariable(value = "journeyId") UUID journeyId, 158 | @RequestBody SpacecraftLocationOverTime[] readings) { 159 | LOGGER.debug("Saving location reading(s) for spacecraft {} and journey {}", spacecraftName, journeyId); 160 | if (null != readings && readings.length > 0) { 161 | astraService.insertLocationReading(readings); 162 | } 163 | return ResponseEntity.ok("OK"); 164 | } 165 | 166 | @PostMapping(value="/pressure", consumes = APPLICATION_JSON_VALUE) 167 | @ApiOperation(value = "Save pressure reading for a journey", response = List.class) 168 | @ApiResponse(code = 200, message = "Saved pressure reading for a journey") 169 | public ResponseEntity savePressureReadings( 170 | @ApiParam(name="spacecraftName", value="Spacecraft name",example = "gemini3",required=true ) 171 | @PathVariable(value = "spacecraftName") String spacecraftName, 172 | @ApiParam(name="journeyId", value="Identifier for journey",example = "abb7c000-c310-11ac-8080-808080808080",required=true ) 173 | @PathVariable(value = "journeyId") UUID journeyId, 174 | @RequestBody SpacecraftPressureOverTime[] readings) { 175 | LOGGER.debug("Saving pressure readings for spacecraft {} and journey {}", spacecraftName, journeyId); 176 | if (null != readings && readings.length > 0) { 177 | astraService.insertPressureReading(readings); 178 | } 179 | return ResponseEntity.ok("OK"); 180 | } 181 | 182 | @PostMapping(value="/speed", consumes = APPLICATION_JSON_VALUE) 183 | @ApiOperation(value = "Save speed reading for a journey", response = List.class) 184 | @ApiResponse(code = 200, message = "Saved speed reading for a journey") 185 | public ResponseEntity saveSpeedReadings( 186 | @ApiParam(name="spacecraftName", value="Spacecraft name",example = "gemini3",required=true ) 187 | @PathVariable(value = "spacecraftName") String spacecraftName, 188 | @ApiParam(name="journeyId", value="Identifier for journey",example = "abb7c000-c310-11ac-8080-808080808080",required=true ) 189 | @PathVariable(value = "journeyId") UUID journeyId, 190 | @RequestBody SpacecraftSpeedOverTime[] readings) { 191 | LOGGER.debug("Saving speed readings for spacecraft {} and journey {}", spacecraftName, journeyId); 192 | if (null != readings && readings.length > 0) { 193 | astraService.insertSpeedReading(readings); 194 | } 195 | return ResponseEntity.ok("OK"); 196 | } 197 | 198 | } 199 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/controller/SpacecraftController.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.controller; 2 | 3 | import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; 4 | import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; 5 | import static org.springframework.web.bind.annotation.RequestMethod.GET; 6 | 7 | import java.net.URI; 8 | import java.util.List; 9 | import java.util.Optional; 10 | import java.util.UUID; 11 | 12 | import javax.servlet.http.HttpServletRequest; 13 | 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.springframework.http.MediaType; 17 | import org.springframework.http.ResponseEntity; 18 | import org.springframework.web.bind.annotation.CrossOrigin; 19 | import org.springframework.web.bind.annotation.GetMapping; 20 | import org.springframework.web.bind.annotation.PathVariable; 21 | import org.springframework.web.bind.annotation.PostMapping; 22 | import org.springframework.web.bind.annotation.RequestBody; 23 | import org.springframework.web.bind.annotation.RequestMapping; 24 | import org.springframework.web.bind.annotation.RestController; 25 | import org.springframework.web.servlet.support.ServletUriComponentsBuilder; 26 | 27 | import com.datastax.astra.entity.SpacecraftJourneyCatalog; 28 | import com.datastax.astra.service.AstraService; 29 | 30 | import io.swagger.annotations.Api; 31 | import io.swagger.annotations.ApiImplicitParam; 32 | import io.swagger.annotations.ApiImplicitParams; 33 | import io.swagger.annotations.ApiOperation; 34 | import io.swagger.annotations.ApiParam; 35 | import io.swagger.annotations.ApiResponse; 36 | import io.swagger.annotations.ApiResponses; 37 | 38 | /** 39 | * REST Controller for operations on spacecrafts catalog.O 40 | */ 41 | @CrossOrigin 42 | @RestController 43 | @Api( 44 | value = "/api/spacecraft", 45 | description = "Operations on spacecrafts catalog") 46 | @RequestMapping("/api/spacecraft") 47 | public class SpacecraftController { 48 | 49 | /** Logger for the class. */ 50 | private static final Logger LOGGER = LoggerFactory.getLogger(SpacecraftController.class); 51 | 52 | /** Service implementation Injection. */ 53 | private AstraService astraService; 54 | 55 | /** 56 | * Constructor. 57 | * 58 | * @param spacecraftService 59 | * service implementation 60 | */ 61 | public SpacecraftController(AstraService spacecraftService) { 62 | this.astraService = spacecraftService; 63 | } 64 | 65 | /** 66 | * List all spacecrafts from the catalog 67 | * 68 | * @return 69 | * list all {@link SpacecraftJourneyCatalog} available in the table 70 | */ 71 | @GetMapping(produces = APPLICATION_JSON_VALUE) 72 | @ApiOperation(value = "List all spacecrafts and journeys", response = List.class) 73 | @ApiResponse(code = 200, message = "List all journeys for a spacecraft") 74 | public ResponseEntity> findAllSpacecrafts() { 75 | LOGGER.info("Retrieving all spacecrafts"); 76 | return ResponseEntity.ok(astraService.findAllSpacecrafts()); 77 | } 78 | 79 | /** 80 | * List all journeys for a dedicated spacecraft. If the spacecraft is not found we will show an empty list (an dnot 404.) 81 | * 82 | * @param spacecraft_name 83 | * spacecraft_name to locate journeys 84 | * @return 85 | * list of associated journey, can be empty 86 | */ 87 | @GetMapping(value = "/{spacecraftName}", produces = APPLICATION_JSON_VALUE) 88 | @ApiOperation(value = "List all journeys for a dedicated spacecraft name", response = List.class) 89 | @ApiResponse(code = 200, message = "List all journeys for a dedicated spacecraft name") 90 | public ResponseEntity> findAllJourneysForSpacecraft( 91 | @ApiParam(name="spacecraftName", value="Spacecraft name",example = "gemini3",required=true ) 92 | @PathVariable(value = "spacecraftName") String spaceCraftName) { 93 | LOGGER.info("Retrieving all journey for spacecraft {}", spaceCraftName); 94 | return ResponseEntity.ok(astraService.findAllJourneysForSpacecraft(spaceCraftName)); 95 | } 96 | 97 | /** 98 | * Find a unique spacecraft journey from its reference. 99 | * 100 | * @param spacecraft_name 101 | * spacecraft_name to locate journeys 102 | * @return 103 | * list of associated journey, can be empty 104 | */ 105 | @RequestMapping( 106 | value = "/{spacecraftName}/{journeyId}", 107 | method = GET, 108 | produces = APPLICATION_JSON_VALUE) 109 | @ApiOperation(value = "Retrieve a journey from its spacecraftname and journeyid", response = List.class) 110 | @ApiResponses({ 111 | @ApiResponse(code = 200, message = "Returnings SpacecraftJourneyCatalog"), 112 | @ApiResponse(code = 400, message = "spacecraftName is blank or contains invalid characters (expecting AlphaNumeric)"), 113 | @ApiResponse(code = 404, message = "No journey exists for the provided spacecraftName and journeyid") 114 | }) 115 | public ResponseEntity findSpacecraftJourney( 116 | @ApiParam(name="spacecraftName", value="Spacecraft name",example = "gemini3",required=true ) 117 | @PathVariable(value = "spacecraftName") String spacecraftName, 118 | @ApiParam(name="journeyId", value="Identifer for journey",example = "abb7c000-c310-11ac-8080-808080808080",required=true ) 119 | @PathVariable(value = "journeyId") UUID journeyId) { 120 | LOGGER.info("Fetching journey with spacecraft name {} and journeyid {}", spacecraftName, journeyId); 121 | // Invoking Service 122 | Optional journey = astraService.findJourneyById(spacecraftName, journeyId); 123 | // Routing Result 124 | if (!journey.isPresent()) { 125 | LOGGER.warn("Journey with spacecraft name {} and journeyid {} has not been found", spacecraftName, journeyId); 126 | return ResponseEntity.notFound().build(); 127 | } 128 | return ResponseEntity.ok(journey.get()); 129 | } 130 | 131 | /** 132 | * Create a new Journey for a Spacecraft 133 | */ 134 | @PostMapping(value = "/{spacecraftName}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = TEXT_PLAIN_VALUE) 135 | @ApiOperation(value = " Create a new Journey for a Spacecraft", response = String.class) 136 | @ApiResponses({ 137 | @ApiResponse(code = 201, message = "Journey has been created"), 138 | @ApiResponse(code = 400, message = "Invalid Spacecraft name provided") 139 | }) 140 | @ApiImplicitParams({ 141 | @ApiImplicitParam( 142 | name = "summary", 143 | value = "Body of the request is a string representing the summary", 144 | required = true, dataType = "string", paramType = "body", example = "Going to the Moon") 145 | }) 146 | public ResponseEntity createSpacecraftJourney( 147 | HttpServletRequest request, 148 | @ApiParam(name="spacecraftName", value="Spacecraft name",example = "soyuztm-8",required=true ) 149 | @PathVariable(value = "spacecraftName") String spacecraftName, 150 | @RequestBody String summary) { 151 | UUID journeyId = astraService.createSpacecraftJourney(spacecraftName, summary); 152 | // HTTP Created spec, return target resource in 'location' header 153 | URI location = ServletUriComponentsBuilder.fromRequestUri(request) 154 | .replacePath("/api/spacecrafts/{spacecraftName}/{journeyId}") 155 | .buildAndExpand(spacecraftName, journeyId) 156 | .toUri(); 157 | // HTTP 201 with confirmation number 158 | return ResponseEntity.created(location).body(journeyId.toString()); 159 | } 160 | 161 | 162 | } 163 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/dao/SessionManager.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.dao; 2 | 3 | import java.io.FileNotFoundException; 4 | import java.nio.file.Paths; 5 | 6 | import com.datastax.astra.utils.CqlFileUtils; 7 | import com.datastax.oss.driver.api.core.CqlSession; 8 | 9 | /** 10 | * {@link CqlSession} will be created for each call and drop after. 11 | */ 12 | public class SessionManager { 13 | 14 | /** Singleton Pattern. */ 15 | private static SessionManager _instance = null; 16 | 17 | /** Connectivity Attributes. */ 18 | private String userName; 19 | private String password; 20 | private String keySpace; 21 | private String secureConnectionBundlePath; 22 | 23 | /** Status and working session. */ 24 | private boolean initialized = false; 25 | private CqlSession cqlSession; 26 | 27 | public static final String QUERY_HEALTH_CHECK = "select data_center from system.local"; 28 | 29 | /** 30 | * Utility Method to initialized parameters. 31 | * 32 | * @return 33 | * singletong of the session Manager 34 | */ 35 | public static synchronized SessionManager getInstance() { 36 | if (null == _instance) { 37 | _instance = new SessionManager(); 38 | } 39 | return _instance; 40 | } 41 | 42 | /** 43 | * Initialize parameters. 44 | * 45 | * @param userName 46 | * current username 47 | * @param password 48 | * current password 49 | * @param secureConnectionBundlePath 50 | * zip bundle path on disl 51 | * @param keyspace 52 | * current keyspace 53 | */ 54 | public void saveCredentials(String userName, String password, String keyspace, String secureConnectionBundlePath) { 55 | this.userName = userName; 56 | this.password = password; 57 | this.keySpace = keyspace; 58 | this.secureConnectionBundlePath = secureConnectionBundlePath; 59 | this.initialized = true; 60 | } 61 | 62 | /** 63 | * Test with no persistence. 64 | * 65 | * @param user 66 | * sample user name 67 | * @param password 68 | * sample password 69 | * @param keyspace 70 | * sample keyspace 71 | * @param secureConnectionBundlePath 72 | * temp file 73 | */ 74 | public void testCredentials(String user, String passwd, String keyspce, String secureConnectionBundlePath) { 75 | // Autocloseable temporary session 76 | try (CqlSession tmpSession = CqlSession.builder() 77 | .withCloudSecureConnectBundle(Paths.get(secureConnectionBundlePath)) 78 | .withAuthCredentials(user, passwd) 79 | .withKeyspace(keyspce).build()) { 80 | tmpSession.execute(QUERY_HEALTH_CHECK); 81 | } catch(RuntimeException re) { 82 | throw new IllegalStateException(re); 83 | } 84 | } 85 | 86 | /** 87 | * Getter accessor for attribute 'cqlSession'. 88 | * 89 | * @return 90 | * current value of 'cqlSession' 91 | */ 92 | public CqlSession connectToAstra() { 93 | if (!isInitialized()) { 94 | throw new IllegalStateException("Please initialize the connection parameters first with saveCredentials(...)"); 95 | } 96 | if (null == cqlSession) { 97 | cqlSession = CqlSession.builder().withCloudSecureConnectBundle(Paths.get(getSecureConnectionBundlePath())) 98 | .withAuthCredentials(getUserName(),getPassword()) 99 | .withKeyspace(getKeySpace()) 100 | .build(); 101 | 102 | // Once session has been initialized, creating schema 103 | createSchemaIfNeeded(cqlSession); 104 | } 105 | return cqlSession; 106 | } 107 | 108 | protected void createSchemaIfNeeded(CqlSession cqlSession) { 109 | try { 110 | CqlFileUtils.executeCQLFile(cqlSession, "schema.cql"); 111 | } catch (FileNotFoundException e) { 112 | throw new IllegalStateException(e); 113 | } 114 | } 115 | 116 | /** 117 | * IfO simple command failing => invalid connection 118 | */ 119 | public void checkConnection() { 120 | try { 121 | connectToAstra().execute(QUERY_HEALTH_CHECK); 122 | } catch(RuntimeException re) { 123 | throw new IllegalStateException(re); 124 | } 125 | } 126 | 127 | /** 128 | * Cleanup session 129 | */ 130 | public void close() { 131 | if (isInitialized() && null != cqlSession) { 132 | cqlSession.close(); 133 | } 134 | } 135 | 136 | /** 137 | * Getter accessor for attribute 'userName'. 138 | * 139 | * @return 140 | * current value of 'userName' 141 | */ 142 | public String getUserName() { 143 | return userName; 144 | } 145 | 146 | /** 147 | * Getter accessor for attribute 'password'. 148 | * 149 | * @return 150 | * current value of 'password' 151 | */ 152 | public String getPassword() { 153 | return password; 154 | } 155 | 156 | /** 157 | * Getter accessor for attribute 'secureConnectionBundlePath'. 158 | * 159 | * @return 160 | * current value of 'secureConnectionBundlePath' 161 | */ 162 | public String getSecureConnectionBundlePath() { 163 | return secureConnectionBundlePath; 164 | } 165 | 166 | /** 167 | * Getter accessor for attribute 'keySpace'. 168 | * 169 | * @return 170 | * current value of 'keySpace' 171 | */ 172 | public String getKeySpace() { 173 | return keySpace; 174 | } 175 | 176 | /** 177 | * Getter accessor for attribute 'initialized'. 178 | * 179 | * @return 180 | * current value of 'initialized' 181 | */ 182 | public boolean isInitialized() { 183 | return initialized; 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/dao/SpacecraftInstrumentsDao.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.dao; 2 | 3 | import java.util.Optional; 4 | import java.util.UUID; 5 | import java.util.concurrent.CompletionStage; 6 | import java.util.function.Function; 7 | 8 | import com.datastax.astra.entity.SpacecraftLocationOverTime; 9 | import com.datastax.astra.entity.SpacecraftPressureOverTime; 10 | import com.datastax.astra.entity.SpacecraftSpeedOverTime; 11 | import com.datastax.astra.entity.SpacecraftTemperatureOverTime; 12 | import com.datastax.oss.driver.api.core.PagingIterable; 13 | import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder; 14 | import com.datastax.oss.driver.api.mapper.annotations.Dao; 15 | import com.datastax.oss.driver.api.mapper.annotations.Insert; 16 | import com.datastax.oss.driver.api.mapper.annotations.QueryProvider; 17 | import com.datastax.oss.driver.api.mapper.annotations.Select; 18 | 19 | /** 20 | * Operation to work with instruments 21 | */ 22 | @Dao 23 | public interface SpacecraftInstrumentsDao { 24 | 25 | /** 26 | * Search for temperature readings with MAPPER 27 | */ 28 | @Select(customWhereClause = "spacecraft_name= :spacecraftName AND journey_id= :journeyId") 29 | PagingIterable getTemperatureReading( 30 | String spacecraftName, UUID JourneyId, 31 | Function setAttributes); 32 | 33 | /** 34 | * Search for temperature readings with QUERY PROVIDER 35 | */ 36 | @QueryProvider(providerClass = SpacecraftInstrumentsQueryProvider.class, 37 | entityHelpers = { SpacecraftTemperatureOverTime.class, SpacecraftPressureOverTime.class, 38 | SpacecraftLocationOverTime.class, SpacecraftSpeedOverTime.class}) 39 | PagingIterable getTemperatureReading( 40 | String spacecraftName, UUID JourneyId, 41 | Optional pageSize, 42 | Optional pagingState); 43 | 44 | /** 45 | * Upsert a temperature reading. 46 | * 47 | * @param reading 48 | * The temperature reading 49 | * @return 50 | * if statement was applied 51 | */ 52 | @Insert 53 | boolean upsertTemperature(SpacecraftTemperatureOverTime reading); 54 | 55 | /** 56 | * Bulk inserts of temperature readings. 57 | * 58 | * @param reading 59 | * The temperature readings 60 | */ 61 | @QueryProvider(providerClass = SpacecraftInstrumentsQueryProvider.class, 62 | entityHelpers = { SpacecraftTemperatureOverTime.class, SpacecraftPressureOverTime.class, 63 | SpacecraftLocationOverTime.class, SpacecraftSpeedOverTime.class}) 64 | CompletionStage insertTemperatureReadingAsync(SpacecraftTemperatureOverTime[] readings); 65 | 66 | @QueryProvider(providerClass = SpacecraftInstrumentsQueryProvider.class, 67 | entityHelpers = { SpacecraftTemperatureOverTime.class, SpacecraftPressureOverTime.class, 68 | SpacecraftLocationOverTime.class, SpacecraftSpeedOverTime.class}) 69 | CompletionStage insertLocationReadingAsync(SpacecraftLocationOverTime[] readings); 70 | 71 | @QueryProvider(providerClass = SpacecraftInstrumentsQueryProvider.class, 72 | entityHelpers = { SpacecraftTemperatureOverTime.class, SpacecraftPressureOverTime.class, 73 | SpacecraftLocationOverTime.class, SpacecraftSpeedOverTime.class}) 74 | CompletionStage insertPressureReadingAsync(SpacecraftPressureOverTime[] readings); 75 | 76 | @QueryProvider(providerClass = SpacecraftInstrumentsQueryProvider.class, 77 | entityHelpers = { SpacecraftTemperatureOverTime.class, SpacecraftPressureOverTime.class, 78 | SpacecraftLocationOverTime.class, SpacecraftSpeedOverTime.class}) 79 | CompletionStage insertSpeedReadingAsync(SpacecraftSpeedOverTime[] readings); 80 | 81 | 82 | 83 | 84 | /** 85 | * Upsert a location reading. 86 | * 87 | * @param reading 88 | * The location reading 89 | * @return 90 | * if statement was applied 91 | */ 92 | @Insert 93 | boolean upsertLocation(SpacecraftLocationOverTime reading); 94 | 95 | /** 96 | * Upsert a pressure reading. 97 | * 98 | * @param reading 99 | * The pressure reading 100 | * @return 101 | * if statement was applied 102 | */ 103 | @Insert 104 | boolean upsertPressure(SpacecraftPressureOverTime reading); 105 | 106 | /** 107 | * Upsert a speed reading. 108 | * 109 | * @param reading 110 | * The speed reading 111 | * @return 112 | * if statement was applied 113 | */ 114 | @Insert 115 | boolean upsertSpeed(SpacecraftSpeedOverTime reading); 116 | 117 | /** 118 | * Search for pressure readings. 119 | */ 120 | @QueryProvider(providerClass = SpacecraftInstrumentsQueryProvider.class, 121 | entityHelpers = { SpacecraftTemperatureOverTime.class, SpacecraftPressureOverTime.class, 122 | SpacecraftLocationOverTime.class, SpacecraftSpeedOverTime.class}) 123 | PagingIterable getPressureReading( 124 | String spacecraftName, UUID JourneyId, Optional pageSize, Optional pagingState); 125 | 126 | /** 127 | * Search for speed readings. 128 | */ 129 | @QueryProvider(providerClass = SpacecraftInstrumentsQueryProvider.class, 130 | entityHelpers = { SpacecraftTemperatureOverTime.class, SpacecraftPressureOverTime.class, 131 | SpacecraftLocationOverTime.class, SpacecraftSpeedOverTime.class}) 132 | PagingIterable getSpeedReading( 133 | String spacecraftName, UUID JourneyId, Optional pageSize, Optional spagingState); 134 | 135 | /** 136 | * Search for location readings. 137 | */ 138 | @QueryProvider(providerClass = SpacecraftInstrumentsQueryProvider.class, 139 | entityHelpers = { SpacecraftTemperatureOverTime.class, SpacecraftPressureOverTime.class, 140 | SpacecraftLocationOverTime.class, SpacecraftSpeedOverTime.class}) 141 | PagingIterable getLocationReading( 142 | String spacecraftName, UUID JourneyId, Optional pageSize, Optional pagingState); 143 | 144 | /** 145 | * Insert instruments. 146 | */ 147 | @QueryProvider(providerClass = SpacecraftInstrumentsQueryProvider.class, 148 | entityHelpers = { SpacecraftTemperatureOverTime.class, SpacecraftPressureOverTime.class, 149 | SpacecraftLocationOverTime.class, SpacecraftSpeedOverTime.class}) 150 | void insertInstruments( 151 | SpacecraftTemperatureOverTime temperature, SpacecraftPressureOverTime pressure, 152 | SpacecraftSpeedOverTime speed, SpacecraftLocationOverTime location); 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/dao/SpacecraftInstrumentsQueryProvider.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.dao; 2 | 3 | import static com.datastax.astra.entity.AbstractInstrumentReading.COLUMN_JOURNEY_ID; 4 | import static com.datastax.astra.entity.AbstractInstrumentReading.COLUMN_SPACECRAFT_NAME; 5 | import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; 6 | import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; 7 | import static com.datastax.oss.driver.api.querybuilder.relation.Relation.column; 8 | 9 | import java.util.Arrays; 10 | import java.util.Optional; 11 | import java.util.UUID; 12 | import java.util.concurrent.CompletionStage; 13 | 14 | import com.datastax.astra.entity.SpacecraftLocationOverTime; 15 | import com.datastax.astra.entity.SpacecraftPressureOverTime; 16 | import com.datastax.astra.entity.SpacecraftSpeedOverTime; 17 | import com.datastax.astra.entity.SpacecraftTemperatureOverTime; 18 | import com.datastax.oss.driver.api.core.CqlSession; 19 | import com.datastax.oss.driver.api.core.PagingIterable; 20 | import com.datastax.oss.driver.api.core.cql.BatchStatement; 21 | import com.datastax.oss.driver.api.core.cql.BatchStatementBuilder; 22 | import com.datastax.oss.driver.api.core.cql.BoundStatement; 23 | import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder; 24 | import com.datastax.oss.driver.api.core.cql.DefaultBatchType; 25 | import com.datastax.oss.driver.api.core.cql.PreparedStatement; 26 | import com.datastax.oss.driver.api.core.cql.ResultSet; 27 | import com.datastax.oss.driver.api.mapper.MapperContext; 28 | import com.datastax.oss.driver.api.mapper.annotations.QueryProvider; 29 | import com.datastax.oss.driver.api.mapper.entity.EntityHelper; 30 | import com.datastax.oss.driver.api.mapper.entity.saving.NullSavingStrategy; 31 | import com.datastax.oss.protocol.internal.util.Bytes; 32 | 33 | /** 34 | * Implementation of Dynamic queries. 35 | * 36 | */ 37 | public class SpacecraftInstrumentsQueryProvider { 38 | 39 | private CqlSession cqlSession; 40 | 41 | /** Helper for bean, tables mappings. */ 42 | private EntityHelper ehTemperature; 43 | private EntityHelper ehPressure; 44 | private EntityHelper ehLocation; 45 | private EntityHelper ehSpeed; 46 | 47 | /** Statements Against Astra. */ 48 | private PreparedStatement psInsertTemperatureReading; 49 | private PreparedStatement psInsertPressureReading; 50 | private PreparedStatement psInsertLocationReading; 51 | private PreparedStatement psInsertSpeedReading; 52 | 53 | private PreparedStatement psSelectTemperatureReading; 54 | private PreparedStatement psSelectPressureReading; 55 | private PreparedStatement psSelectLocationReading; 56 | private PreparedStatement psSelectSpeedReading; 57 | 58 | /** 59 | * Constructor invoked by the DataStax driver based on Annotation {@link QueryProvider} 60 | * set on class {@link SpacecraftInstrumentsDao}. 61 | * 62 | * @param context 63 | * context to extrat dse session 64 | * @param ehTemperature 65 | * entity helper to interact with bean {@link SpacecraftTemperatureOverTime} 66 | * @param ehPressure 67 | * entity helper to interact with bean {@link SpacecraftPressureOverTime} 68 | * @param ehLocation 69 | * entity helper to interact with bean {@link SpacecraftLocationOverTime} 70 | */ 71 | public SpacecraftInstrumentsQueryProvider(MapperContext context, 72 | EntityHelper ehTemperature, 73 | EntityHelper ehPressure, 74 | EntityHelper ehLocation, 75 | EntityHelper ehSpeed) { 76 | this.cqlSession = context.getSession(); 77 | this.ehTemperature = ehTemperature; 78 | this.ehPressure = ehPressure; 79 | this.ehSpeed = ehSpeed; 80 | this.ehLocation = ehLocation; 81 | 82 | // Leveraging EntityHelper for insert queries 83 | psInsertTemperatureReading = cqlSession.prepare(ehTemperature.insert().asCql()); 84 | psInsertPressureReading = cqlSession.prepare(ehPressure.insert().asCql()); 85 | psInsertLocationReading = cqlSession.prepare(ehLocation.insert().asCql()); 86 | psInsertSpeedReading = cqlSession.prepare(ehSpeed.insert().asCql()); 87 | 88 | psSelectTemperatureReading = cqlSession.prepare( 89 | selectFrom(SpacecraftTemperatureOverTime.TABLE_NAME).all() 90 | .where(column(COLUMN_SPACECRAFT_NAME).isEqualTo(bindMarker(COLUMN_SPACECRAFT_NAME))) 91 | .where(column(COLUMN_JOURNEY_ID).isEqualTo(bindMarker(COLUMN_JOURNEY_ID))) 92 | .build()); 93 | psSelectPressureReading = cqlSession.prepare( 94 | selectFrom(SpacecraftPressureOverTime.TABLE_NAME).all() 95 | .where(column(COLUMN_SPACECRAFT_NAME).isEqualTo(bindMarker(COLUMN_SPACECRAFT_NAME))) 96 | .where(column(COLUMN_JOURNEY_ID).isEqualTo(bindMarker(COLUMN_JOURNEY_ID))) 97 | .build()); 98 | psSelectSpeedReading = cqlSession.prepare( 99 | selectFrom(SpacecraftSpeedOverTime.TABLE_NAME).all() 100 | .where(column(COLUMN_SPACECRAFT_NAME).isEqualTo(bindMarker(COLUMN_SPACECRAFT_NAME))) 101 | .where(column(COLUMN_JOURNEY_ID).isEqualTo(bindMarker(COLUMN_JOURNEY_ID))) 102 | .build()); 103 | psSelectLocationReading = cqlSession.prepare( 104 | selectFrom(SpacecraftLocationOverTime.TABLE_NAME).all() 105 | .where(column(COLUMN_SPACECRAFT_NAME).isEqualTo(bindMarker(COLUMN_SPACECRAFT_NAME))) 106 | .where(column(COLUMN_JOURNEY_ID).isEqualTo(bindMarker(COLUMN_JOURNEY_ID))) 107 | .build()); 108 | } 109 | 110 | /** 111 | * Insert instruments values for a timestamp. 112 | */ 113 | public void insertInstruments( 114 | SpacecraftTemperatureOverTime temperature, SpacecraftPressureOverTime pressure, 115 | SpacecraftSpeedOverTime speed, SpacecraftLocationOverTime location) { 116 | cqlSession.executeAsync(BatchStatement.builder(DefaultBatchType.LOGGED) 117 | .addStatement(bind(psInsertTemperatureReading, temperature, ehTemperature)) 118 | .addStatement(bind(psInsertPressureReading, pressure, ehPressure)) 119 | .addStatement(bind(psInsertSpeedReading, speed, ehSpeed)) 120 | .addStatement(bind(psInsertLocationReading, location, ehLocation)) 121 | .build()).thenApply(rs -> null); 122 | } 123 | 124 | public CompletionStage insertLocationReadingAsync(SpacecraftLocationOverTime[] readings) { 125 | BatchStatementBuilder myBatch = BatchStatement.builder(DefaultBatchType.LOGGED); 126 | Arrays.stream(readings).forEach(read -> myBatch.addStatement(bind(psInsertLocationReading, read, ehLocation))); 127 | return cqlSession.executeAsync(myBatch.build()).thenApply(rs -> rs.wasApplied()); 128 | } 129 | 130 | public CompletionStage insertTemperatureReadingAsync(SpacecraftTemperatureOverTime[] readings) { 131 | BatchStatementBuilder myBatch = BatchStatement.builder(DefaultBatchType.LOGGED); 132 | Arrays.stream(readings).forEach(read -> myBatch.addStatement(bind(psInsertTemperatureReading, read, ehTemperature))); 133 | return cqlSession.executeAsync(myBatch.build()).thenApply(rs -> rs.wasApplied()); 134 | } 135 | 136 | public CompletionStage insertPressureReadingAsync(SpacecraftPressureOverTime[] readings) { 137 | BatchStatementBuilder myBatch = BatchStatement.builder(DefaultBatchType.LOGGED); 138 | Arrays.stream(readings).forEach(read -> myBatch.addStatement(bind(psInsertPressureReading, read, ehPressure))); 139 | return cqlSession.executeAsync(myBatch.build()).thenApply(rs -> rs.wasApplied()); 140 | } 141 | 142 | public CompletionStage insertSpeedReadingAsync(SpacecraftSpeedOverTime[] readings) { 143 | BatchStatementBuilder myBatch = BatchStatement.builder(DefaultBatchType.LOGGED); 144 | Arrays.stream(readings).forEach(read -> myBatch.addStatement(bind(psInsertSpeedReading, read, ehSpeed))); 145 | return cqlSession.executeAsync(myBatch.build()).thenApplyAsync(rs -> rs.wasApplied()); 146 | } 147 | 148 | /** 149 | * Retrieve Temperature reading for a journey. 150 | */ 151 | public PagingIterable getTemperatureReading( 152 | String spacecraftName, 153 | UUID journeyId, 154 | Optional pageSize, 155 | Optional pagingState) { 156 | 157 | // Detailing operations for the first (next will be much compact) 158 | 159 | // (1) - Bind the prepared statement with parameters 160 | BoundStatement bsTemperature = psSelectTemperatureReading.bind() 161 | .setUuid(COLUMN_JOURNEY_ID, journeyId) 162 | .setString(COLUMN_SPACECRAFT_NAME, spacecraftName); 163 | 164 | // (2) - Update the bound statement to add paging metadata (pageSize, pageState) 165 | bsTemperature = paging(bsTemperature, pageSize, pagingState); 166 | 167 | // (3) - Executing query 168 | ResultSet resultSet = cqlSession.execute(bsTemperature); 169 | 170 | // (4) - Using the entity Help to marshall to expect bean 171 | return resultSet.map(ehTemperature::get); 172 | } 173 | 174 | /** 175 | * Retrieve Pressure reading for a journey. 176 | */ 177 | public PagingIterable getPressureReading( 178 | String spacecraftName, UUID journeyId, Optional pageSize, Optional pagingState) { 179 | return cqlSession.execute(paging(psSelectPressureReading.bind() 180 | .setUuid(COLUMN_JOURNEY_ID, journeyId) 181 | .setString(COLUMN_SPACECRAFT_NAME, spacecraftName), pageSize, pagingState)) 182 | .map(ehPressure::get); 183 | } 184 | 185 | /** 186 | * Retrieve Location reading for a journey. 187 | */ 188 | public PagingIterable getLocationReading( 189 | String spacecraftName, UUID journeyId, Optional pageSize, Optional pagingState) { 190 | return cqlSession.execute(paging(psSelectLocationReading.bind() 191 | .setUuid(COLUMN_JOURNEY_ID, journeyId) 192 | .setString(COLUMN_SPACECRAFT_NAME, spacecraftName), pageSize, pagingState)) 193 | .map(ehLocation::get); 194 | } 195 | 196 | /** 197 | * Retrieve Pressure reading for a journey. 198 | */ 199 | public PagingIterable getSpeedReading( 200 | String spacecraftName, UUID journeyId, Optional pageSize, Optional pagingState) { 201 | return cqlSession.execute(paging(psSelectSpeedReading.bind() 202 | .setUuid(COLUMN_JOURNEY_ID, journeyId) 203 | .setString(COLUMN_SPACECRAFT_NAME, spacecraftName), pageSize, pagingState)) 204 | .map(ehSpeed::get); 205 | } 206 | 207 | /** 208 | * Syntaxic sugar to help with paging 209 | */ 210 | private BoundStatement paging(BoundStatement bs, Optional pageSize, Optional pagingState) { 211 | if(pageSize.isPresent()) { 212 | bs = bs.setPageSize(pageSize.get()); 213 | } 214 | if (pagingState.isPresent()) { 215 | bs = bs.setPagingState(Bytes.fromHexString(pagingState.get())); 216 | }; 217 | return bs; 218 | } 219 | 220 | /** 221 | * Syntaxic sugar to help with mapping 222 | */ 223 | public static BoundStatement bind(PreparedStatement preparedStatement, T entity, EntityHelper entityHelper) { 224 | BoundStatementBuilder boundStatement = preparedStatement.boundStatementBuilder(); 225 | entityHelper.set(entity, boundStatement, NullSavingStrategy.DO_NOT_SET); 226 | return boundStatement.build(); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/dao/SpacecraftJourneyDao.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.dao; 2 | 3 | import java.util.Optional; 4 | import java.util.UUID; 5 | 6 | import com.datastax.astra.entity.SpacecraftJourneyCatalog; 7 | import com.datastax.oss.driver.api.core.PagingIterable; 8 | import com.datastax.oss.driver.api.mapper.annotations.Dao; 9 | import com.datastax.oss.driver.api.mapper.annotations.Insert; 10 | import com.datastax.oss.driver.api.mapper.annotations.Query; 11 | import com.datastax.oss.driver.api.mapper.annotations.Select; 12 | 13 | /** 14 | * Defining Dao for Spacecraft requests. 15 | */ 16 | @Dao 17 | public interface SpacecraftJourneyDao { 18 | 19 | /** 20 | * Find All spacecraft in the table. 21 | */ 22 | @Query("SELECT * FROM ${keyspaceId}.${tableId}") 23 | PagingIterable findAll(); 24 | 25 | /** 26 | * Find all journeys for a spacecraft name. 27 | * 28 | * @param spacecraftName 29 | * spacecraftname (Partition key) 30 | * @return 31 | * list of journeys 32 | */ 33 | @Select(customWhereClause = SpacecraftJourneyCatalog.COLUMN_SPACECRAFT_NAME + "= :spacecraftName") 34 | PagingIterable findAllJourneysForSpacecraft(String spacecraftName); 35 | 36 | /** 37 | * Find a journey from its id and a spacecraft name (PK) 38 | * 39 | * @param spacecraftName 40 | * spacecraft name 41 | * @param journeyId 42 | * journey unique identifier 43 | * @return 44 | * journey details if it exists or empty 45 | */ 46 | @Select 47 | Optional findById(String spacecraftName, UUID journeyId); 48 | 49 | /** 50 | * Upsert a new journey. 51 | * 52 | * @param spacecraftJourney 53 | * bean representing a journey 54 | * @return 55 | * if statement was applied 56 | */ 57 | @Insert 58 | boolean upsert(SpacecraftJourneyCatalog spacecraftJourney); 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/dao/SpacecraftMapper.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.dao; 2 | 3 | import com.datastax.oss.driver.api.core.CqlIdentifier; 4 | import com.datastax.oss.driver.api.mapper.annotations.Dao; 5 | import com.datastax.oss.driver.api.mapper.annotations.DaoFactory; 6 | import com.datastax.oss.driver.api.mapper.annotations.DaoKeyspace; 7 | import com.datastax.oss.driver.api.mapper.annotations.Mapper; 8 | 9 | /** 10 | * Wrapper for all {@link Dao} generated by DataStax Driver. 11 | */ 12 | @Mapper 13 | public interface SpacecraftMapper { 14 | 15 | /** 16 | * Initialization of Dao {@link RatingDseDao} 17 | * 18 | * @param keyspace 19 | * working keyspace name 20 | * @return 21 | * instanciation with the mappers 22 | */ 23 | @DaoFactory 24 | SpacecraftJourneyDao spacecraftJourneyDao(@DaoKeyspace CqlIdentifier keyspace); 25 | 26 | @DaoFactory 27 | SpacecraftInstrumentsDao spacecraftInstrumentsDao(@DaoKeyspace CqlIdentifier keyspace); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/doc/DocumentationApiConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.doc; 2 | 3 | /* 4 | * #%L 5 | * ff4j-spring-boot-web-api 6 | * %% 7 | * Copyright (C) 2013 - 2016 FF4J 8 | * %% 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * #L% 21 | */ 22 | 23 | import org.springframework.context.annotation.Bean; 24 | import org.springframework.context.annotation.Configuration; 25 | 26 | import com.datastax.astra.GettingStartedWithAstra; 27 | 28 | import springfox.documentation.builders.ApiInfoBuilder; 29 | import springfox.documentation.builders.PathSelectors; 30 | import springfox.documentation.builders.RequestHandlerSelectors; 31 | import springfox.documentation.service.ApiInfo; 32 | import springfox.documentation.spi.DocumentationType; 33 | import springfox.documentation.spring.web.plugins.Docket; 34 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 35 | 36 | /** 37 | * Documentation of the API 38 | * 39 | * @author DataStax Evangelist Team 40 | */ 41 | @Configuration 42 | @EnableSwagger2 43 | public class DocumentationApiConfiguration { 44 | 45 | @Bean 46 | public Docket api() { 47 | return new Docket(DocumentationType.SWAGGER_2) 48 | .groupName("GettingStartedWithAstra") 49 | .select() 50 | .apis(RequestHandlerSelectors.basePackage("com.datastax.astra")) 51 | .paths(PathSelectors.regex("/api.*")) 52 | .build() 53 | .apiInfo(apiInfo()) 54 | .useDefaultResponseMessages(false); 55 | } 56 | 57 | /** 58 | * Initialization of documentation 59 | * 60 | * @return static infos 61 | */ 62 | private ApiInfo apiInfo() { 63 | ApiInfoBuilder builder = new ApiInfoBuilder(); 64 | builder.title("Astra Getting Started Backend API"); 65 | builder.description("Start with Astra in Minute"); 66 | builder.version(GettingStartedWithAstra.class.getPackage().getImplementationVersion()); 67 | return builder.build(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/doc/DocumentationController.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.doc; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RequestMethod; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | /** 8 | * We redirect to the API documentation page 9 | */ 10 | @RestController 11 | @RequestMapping("/") 12 | public class DocumentationController { 13 | 14 | @RequestMapping(value = "/", method = RequestMethod.GET, produces = "text/html") 15 | public String redirectToDoc() { 16 | return new StringBuilder("" 17 | + "" 18 | + " " 19 | + " " 20 | + " " 21 | + " ").toString(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/entity/AbstractInstrumentReading.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.entity; 2 | 3 | import java.time.Instant; 4 | import java.util.UUID; 5 | 6 | import com.datastax.oss.driver.api.mapper.annotations.ClusteringColumn; 7 | import com.datastax.oss.driver.api.mapper.annotations.CqlName; 8 | import com.datastax.oss.driver.api.mapper.annotations.PartitionKey; 9 | 10 | /** 11 | * Mutualized fields for all readings. 12 | */ 13 | public abstract class AbstractInstrumentReading { 14 | 15 | /** Column Names. */ 16 | public static final String COLUMN_SPACECRAFT_NAME = "spacecraft_name"; 17 | public static final String COLUMN_JOURNEY_ID = "journey_id"; 18 | public static final String COLUMN_READING_TIME = "reading_time"; 19 | 20 | @PartitionKey(0) 21 | @CqlName(COLUMN_SPACECRAFT_NAME) 22 | private String spacecraft_name; 23 | 24 | @PartitionKey(1) 25 | @CqlName(COLUMN_JOURNEY_ID) 26 | private UUID journey_id; 27 | 28 | @ClusteringColumn 29 | @CqlName(COLUMN_READING_TIME) 30 | private Instant reading_time; 31 | 32 | /** 33 | * Getter accessor for attribute 'spacecraft_name'. 34 | * 35 | * @return 36 | * current value of 'spacecraft_name' 37 | */ 38 | public String getSpacecraft_name() { 39 | return spacecraft_name; 40 | } 41 | 42 | /** 43 | * Setter accessor for attribute 'spacecraft_name'. 44 | * @param spacecraft_name 45 | * new value for 'spacecraft_name ' 46 | */ 47 | public void setSpacecraft_name(String spacecraft_name) { 48 | this.spacecraft_name = spacecraft_name; 49 | } 50 | 51 | /** 52 | * Getter accessor for attribute 'journey_id'. 53 | * 54 | * @return 55 | * current value of 'journey_id' 56 | */ 57 | public UUID getJourney_id() { 58 | return journey_id; 59 | } 60 | 61 | /** 62 | * Setter accessor for attribute 'journey_id'. 63 | * @param journey_id 64 | * new value for 'journey_id ' 65 | */ 66 | public void setJourney_id(UUID journey_id) { 67 | this.journey_id = journey_id; 68 | } 69 | 70 | /** 71 | * Getter accessor for attribute 'reading_time'. 72 | * 73 | * @return 74 | * current value of 'reading_time' 75 | */ 76 | public Instant getReading_time() { 77 | return reading_time; 78 | } 79 | 80 | /** 81 | * Setter accessor for attribute 'reading_time'. 82 | * @param reading_time 83 | * new value for 'reading_time ' 84 | */ 85 | public void setReading_time(Instant reading_time) { 86 | this.reading_time = reading_time; 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/entity/LocationUdt.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.entity; 2 | 3 | import com.datastax.oss.driver.api.mapper.annotations.CqlName; 4 | import com.datastax.oss.driver.api.mapper.annotations.Entity; 5 | 6 | /** 7 | * Bean for Location UDT.s 8 | */ 9 | @Entity 10 | @CqlName(LocationUdt.UDT_TYPE_NAME) 11 | public class LocationUdt { 12 | 13 | /** Constants for UDT. */ 14 | public static final String UDT_TYPE_NAME = "location"; 15 | public static final String XCOORDINATE = "x_coordinate"; 16 | public static final String YCOORDINATE = "y_coordinate"; 17 | public static final String ZCOORDINATE = "z_coordinate"; 18 | 19 | @CqlName(XCOORDINATE) 20 | private double x_coordinate; 21 | 22 | @CqlName(YCOORDINATE) 23 | private double y_coordinate; 24 | 25 | @CqlName(ZCOORDINATE) 26 | private double z_coordinate; 27 | 28 | /** 29 | * Default constructor. 30 | */ 31 | public LocationUdt() {} 32 | 33 | /** 34 | * Init location with coordinates. 35 | */ 36 | public LocationUdt(double x, double y, double z) { 37 | this.x_coordinate = x; 38 | this.y_coordinate = y; 39 | this.z_coordinate = z; 40 | } 41 | 42 | /** 43 | * Getter accessor for attribute 'x_coordinate'. 44 | * 45 | * @return 46 | * current value of 'x_coordinate' 47 | */ 48 | public double getX_coordinate() { 49 | return x_coordinate; 50 | } 51 | 52 | /** 53 | * Setter accessor for attribute 'x_coordinate'. 54 | * @param x_coordinate 55 | * new value for 'x_coordinate ' 56 | */ 57 | public void setX_coordinate(double x_coordinate) { 58 | this.x_coordinate = x_coordinate; 59 | } 60 | 61 | /** 62 | * Getter accessor for attribute 'y_coordinate'. 63 | * 64 | * @return 65 | * current value of 'y_coordinate' 66 | */ 67 | public double getY_coordinate() { 68 | return y_coordinate; 69 | } 70 | 71 | /** 72 | * Setter accessor for attribute 'y_coordinate'. 73 | * @param y_coordinate 74 | * new value for 'y_coordinate ' 75 | */ 76 | public void setY_coordinate(double y_coordinate) { 77 | this.y_coordinate = y_coordinate; 78 | } 79 | 80 | /** 81 | * Getter accessor for attribute 'z_coordinate'. 82 | * 83 | * @return 84 | * current value of 'z_coordinate' 85 | */ 86 | public double getZ_coordinate() { 87 | return z_coordinate; 88 | } 89 | 90 | /** 91 | * Setter accessor for attribute 'z_coordinate'. 92 | * @param z_coordinate 93 | * new value for 'z_coordinate ' 94 | */ 95 | public void setZ_coordinate(double z_coordinate) { 96 | this.z_coordinate = z_coordinate; 97 | } 98 | 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/entity/SpacecraftJourneyCatalog.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.entity; 2 | 3 | import java.io.Serializable; 4 | import java.time.Instant; 5 | import java.util.UUID; 6 | 7 | import com.datastax.oss.driver.api.core.CqlIdentifier; 8 | import com.datastax.oss.driver.api.core.CqlSession; 9 | import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder; 10 | import com.datastax.oss.driver.api.core.type.DataTypes; 11 | import com.datastax.oss.driver.api.mapper.annotations.ClusteringColumn; 12 | import com.datastax.oss.driver.api.mapper.annotations.CqlName; 13 | import com.datastax.oss.driver.api.mapper.annotations.Entity; 14 | import com.datastax.oss.driver.api.mapper.annotations.PartitionKey; 15 | import com.datastax.oss.driver.api.querybuilder.SchemaBuilder; 16 | import com.fasterxml.jackson.annotation.JsonProperty; 17 | 18 | 19 | /** 20 | * Entity representing Table 21 | */ 22 | @Entity 23 | @CqlName(SpacecraftJourneyCatalog.TABLE_NAME) 24 | public class SpacecraftJourneyCatalog implements Serializable { 25 | 26 | /** Serial. */ 27 | private static final long serialVersionUID = 1L; 28 | 29 | /** Constants.*/ 30 | public static final String TABLE_NAME = "spacecraft_journey_catalog"; 31 | public static final String COLUMN_SPACECRAFT_NAME = "spacecraft_name"; 32 | public static final String COLUMN_ID = "journey_id"; 33 | public static final String COLUMN_START = "start"; 34 | public static final String COLUMN_END = "end"; 35 | public static final String COLUMN_ACTIVE = "active"; 36 | public static final String COLUMN_SUMMARY = "summary"; 37 | 38 | @PartitionKey 39 | @CqlName(COLUMN_SPACECRAFT_NAME) 40 | @JsonProperty(COLUMN_SPACECRAFT_NAME) 41 | private String name; 42 | 43 | @ClusteringColumn 44 | @CqlName(COLUMN_ID) 45 | @JsonProperty(COLUMN_ID) 46 | private UUID journeyId; 47 | 48 | @CqlName(COLUMN_START) 49 | private Instant start; 50 | 51 | @CqlName(COLUMN_END) 52 | private Instant end; 53 | 54 | @CqlName(COLUMN_ACTIVE) 55 | private Boolean active; 56 | 57 | @CqlName(COLUMN_SUMMARY) 58 | private String summary; 59 | 60 | public SpacecraftJourneyCatalog() {} 61 | 62 | /** 63 | * Create table if not exist. 64 | * 65 | * @param session 66 | * current cql session 67 | */ 68 | public static void createTable(CqlSession session) { 69 | session.execute(SchemaBuilder 70 | .createTable(CqlIdentifier.fromCql(TABLE_NAME)) 71 | .ifNotExists() 72 | .withPartitionKey(COLUMN_SPACECRAFT_NAME, DataTypes.TEXT) 73 | .withClusteringColumn(COLUMN_ID, DataTypes.TIMEUUID) 74 | .withColumn(COLUMN_START, DataTypes.TIMESTAMP) 75 | .withColumn(COLUMN_END, DataTypes.TIMESTAMP) 76 | .withColumn(COLUMN_ACTIVE, DataTypes.BOOLEAN) 77 | .withColumn(COLUMN_SUMMARY, DataTypes.TEXT) 78 | .withClusteringOrder(COLUMN_ID, ClusteringOrder.DESC) 79 | .build()); 80 | } 81 | 82 | /** 83 | * Getter accessor for attribute 'name'. 84 | * 85 | * @return 86 | * current value of 'name' 87 | */ 88 | public String getName() { 89 | return name; 90 | } 91 | 92 | /** 93 | * Setter accessor for attribute 'name'. 94 | * @param name 95 | * new value for 'name ' 96 | */ 97 | public void setName(String name) { 98 | this.name = name; 99 | } 100 | 101 | /** 102 | * Getter accessor for attribute 'journeyId'. 103 | * 104 | * @return 105 | * current value of 'journeyId' 106 | */ 107 | public UUID getJourneyId() { 108 | return journeyId; 109 | } 110 | 111 | /** 112 | * Setter accessor for attribute 'journeyId'. 113 | * @param journeyId 114 | * new value for 'journeyId ' 115 | */ 116 | public void setJourneyId(UUID journeyId) { 117 | this.journeyId = journeyId; 118 | } 119 | 120 | /** 121 | * Getter accessor for attribute 'start'. 122 | * 123 | * @return 124 | * current value of 'start' 125 | */ 126 | public Instant getStart() { 127 | return start; 128 | } 129 | 130 | /** 131 | * Setter accessor for attribute 'start'. 132 | * @param start 133 | * new value for 'start ' 134 | */ 135 | public void setStart(Instant start) { 136 | this.start = start; 137 | } 138 | 139 | /** 140 | * Getter accessor for attribute 'end'. 141 | * 142 | * @return 143 | * current value of 'end' 144 | */ 145 | public Instant getEnd() { 146 | return end; 147 | } 148 | 149 | /** 150 | * Setter accessor for attribute 'end'. 151 | * @param end 152 | * new value for 'end ' 153 | */ 154 | public void setEnd(Instant end) { 155 | this.end = end; 156 | } 157 | 158 | /** 159 | * Getter accessor for attribute 'active'. 160 | * 161 | * @return 162 | * current value of 'active' 163 | */ 164 | public Boolean getActive() { 165 | return active; 166 | } 167 | 168 | /** 169 | * Setter accessor for attribute 'active'. 170 | * @param active 171 | * new value for 'active ' 172 | */ 173 | public void setActive(Boolean active) { 174 | this.active = active; 175 | } 176 | 177 | /** 178 | * Getter accessor for attribute 'summary'. 179 | * 180 | * @return 181 | * current value of 'summary' 182 | */ 183 | public String getSummary() { 184 | return summary; 185 | } 186 | 187 | /** 188 | * Setter accessor for attribute 'summary'. 189 | * @param summary 190 | * new value for 'summary ' 191 | */ 192 | public void setSummary(String summary) { 193 | this.summary = summary; 194 | } 195 | 196 | 197 | } 198 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/entity/SpacecraftLocationOverTime.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.entity; 2 | 3 | import com.datastax.oss.driver.api.mapper.annotations.CqlName; 4 | import com.datastax.oss.driver.api.mapper.annotations.Entity; 5 | 6 | /** 7 | * Bean Mapping table spacecraft_temperature_over_time. 8 | */ 9 | @Entity 10 | @CqlName(SpacecraftLocationOverTime.TABLE_NAME) 11 | public class SpacecraftLocationOverTime extends AbstractInstrumentReading { 12 | 13 | /** Constants. */ 14 | public static final String TABLE_NAME = "spacecraft_location_over_time"; 15 | public static final String COLUMN_LOCATION = "location"; 16 | public static final String COLUMN_LOCATION_UNIT = "location_unit"; 17 | 18 | @CqlName(COLUMN_LOCATION) 19 | private LocationUdt location; 20 | 21 | @CqlName(COLUMN_LOCATION_UNIT) 22 | private String location_unit; 23 | 24 | /** 25 | * Getter accessor for attribute 'location'. 26 | * 27 | * @return 28 | * current value of 'location' 29 | */ 30 | public LocationUdt getLocation() { 31 | return location; 32 | } 33 | 34 | /** 35 | * Setter accessor for attribute 'location'. 36 | * @param location 37 | * new value for 'location ' 38 | */ 39 | public void setLocation(LocationUdt location) { 40 | this.location = location; 41 | } 42 | 43 | /** 44 | * Getter accessor for attribute 'location_unit'. 45 | * 46 | * @return 47 | * current value of 'location_unit' 48 | */ 49 | public String getLocation_unit() { 50 | return location_unit; 51 | } 52 | 53 | /** 54 | * Setter accessor for attribute 'location_unit'. 55 | * @param location_unit 56 | * new value for 'location_unit ' 57 | */ 58 | public void setLocation_unit(String location_unit) { 59 | this.location_unit = location_unit; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/entity/SpacecraftPressureOverTime.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.entity; 2 | 3 | import com.datastax.oss.driver.api.mapper.annotations.CqlName; 4 | import com.datastax.oss.driver.api.mapper.annotations.Entity; 5 | 6 | /** 7 | * Bean Mapping table spacecraft_temperature_over_time. 8 | */ 9 | @Entity 10 | @CqlName(SpacecraftPressureOverTime.TABLE_NAME) 11 | public class SpacecraftPressureOverTime extends AbstractInstrumentReading { 12 | 13 | /** Constants. */ 14 | public static final String TABLE_NAME = "spacecraft_pressure_over_time"; 15 | public static final String COLUMN_PRESSURE = "pressure"; 16 | public static final String COLUMN_PRESSURE_UNIT = "pressure_unit"; 17 | 18 | @CqlName(COLUMN_PRESSURE) 19 | private Double pressure; 20 | 21 | @CqlName(COLUMN_PRESSURE_UNIT) 22 | private String pressure_unit; 23 | 24 | /** 25 | * Getter accessor for attribute 'pressure'. 26 | * 27 | * @return 28 | * current value of 'pressure' 29 | */ 30 | public Double getPressure() { 31 | return pressure; 32 | } 33 | 34 | /** 35 | * Setter accessor for attribute 'pressure'. 36 | * @param pressure 37 | * new value for 'pressure ' 38 | */ 39 | public void setPressure(Double pressure) { 40 | this.pressure = pressure; 41 | } 42 | 43 | /** 44 | * Getter accessor for attribute 'pressure_unit'. 45 | * 46 | * @return 47 | * current value of 'pressure_unit' 48 | */ 49 | public String getPressure_unit() { 50 | return pressure_unit; 51 | } 52 | 53 | /** 54 | * Setter accessor for attribute 'pressure_unit'. 55 | * @param pressure_unit 56 | * new value for 'pressure_unit ' 57 | */ 58 | public void setPressure_unit(String pressure_unit) { 59 | this.pressure_unit = pressure_unit; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/entity/SpacecraftSpeedOverTime.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.entity; 2 | 3 | import com.datastax.oss.driver.api.mapper.annotations.CqlName; 4 | import com.datastax.oss.driver.api.mapper.annotations.Entity; 5 | 6 | /** 7 | * Bean Mapping table spacecraft_temperature_over_time. 8 | */ 9 | @Entity 10 | @CqlName(SpacecraftSpeedOverTime.TABLE_NAME) 11 | public class SpacecraftSpeedOverTime extends AbstractInstrumentReading { 12 | 13 | /** Constants. */ 14 | public static final String TABLE_NAME = "spacecraft_speed_over_time"; 15 | public static final String COLUMN_SPEED = "speed"; 16 | public static final String COLUMN_SPEED_UNIT = "speed_unit"; 17 | 18 | @CqlName(COLUMN_SPEED) 19 | private Double speed; 20 | 21 | @CqlName(COLUMN_SPEED_UNIT) 22 | private String speed_unit; 23 | 24 | /** 25 | * Getter accessor for attribute 'speed'. 26 | * 27 | * @return 28 | * current value of 'speed' 29 | */ 30 | public Double getSpeed() { 31 | return speed; 32 | } 33 | 34 | /** 35 | * Setter accessor for attribute 'speed'. 36 | * @param speed 37 | * new value for 'speed ' 38 | */ 39 | public void setSpeed(Double speed) { 40 | this.speed = speed; 41 | } 42 | 43 | /** 44 | * Getter accessor for attribute 'speed_unit'. 45 | * 46 | * @return 47 | * current value of 'speed_unit' 48 | */ 49 | public String getSpeed_unit() { 50 | return speed_unit; 51 | } 52 | 53 | /** 54 | * Setter accessor for attribute 'speed_unit'. 55 | * @param speed_unit 56 | * new value for 'speed_unit ' 57 | */ 58 | public void setSpeed_unit(String speed_unit) { 59 | this.speed_unit = speed_unit; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/entity/SpacecraftTemperatureOverTime.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.entity; 2 | 3 | import com.datastax.oss.driver.api.mapper.annotations.CqlName; 4 | import com.datastax.oss.driver.api.mapper.annotations.Entity; 5 | 6 | /** 7 | * Bean Mapping table spacecraft_temperature_over_time. 8 | */ 9 | @Entity 10 | @CqlName(SpacecraftTemperatureOverTime.TABLE_NAME) 11 | public class SpacecraftTemperatureOverTime extends AbstractInstrumentReading { 12 | 13 | /** Constants. */ 14 | public static final String TABLE_NAME = "spacecraft_temperature_over_time"; 15 | public static final String COLUMN_TEMPERATURE = "temperature"; 16 | public static final String COLUMN_TEMPERATURE_UNIT = "temperature_unit"; 17 | 18 | @CqlName(COLUMN_TEMPERATURE) 19 | private Double temperature; 20 | 21 | @CqlName(COLUMN_TEMPERATURE_UNIT) 22 | private String temperature_unit; 23 | 24 | /** 25 | * Getter accessor for attribute 'temperature'. 26 | * 27 | * @return 28 | * current value of 'temperature' 29 | */ 30 | public Double getTemperature() { 31 | return temperature; 32 | } 33 | 34 | /** 35 | * Setter accessor for attribute 'temperature'. 36 | * @param temperature 37 | * new value for 'temperature ' 38 | */ 39 | public void setTemperature(Double temperature) { 40 | this.temperature = temperature; 41 | } 42 | 43 | /** 44 | * Getter accessor for attribute 'temperature_unit'. 45 | * 46 | * @return 47 | * current value of 'temperature_unit' 48 | */ 49 | public String getTemperature_unit() { 50 | return temperature_unit; 51 | } 52 | 53 | /** 54 | * Setter accessor for attribute 'temperature_unit'. 55 | * @param temperature_unit 56 | * new value for 'temperature_unit ' 57 | */ 58 | public void setTemperature_unit(String temperature_unit) { 59 | this.temperature_unit = temperature_unit; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/model/PagedResultWrapper.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.model; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.ArrayList; 5 | import java.util.Iterator; 6 | import java.util.List; 7 | import java.util.Optional; 8 | import java.util.stream.IntStream; 9 | 10 | import com.datastax.oss.driver.api.core.MappedAsyncPagingIterable; 11 | import com.datastax.oss.driver.api.core.PagingIterable; 12 | import com.datastax.oss.protocol.internal.util.Bytes; 13 | 14 | /** 15 | * Ease usage of the paginState. 16 | * 17 | * @author DataStax Developer Advocates team. 18 | */ 19 | public class PagedResultWrapper < ENTITY > { 20 | 21 | /** Custom management of paging state. */ 22 | private Optional< String > pageState = Optional.empty(); 23 | 24 | /** Custom management of paging state. */ 25 | private Integer pageSize = 1; 26 | 27 | /** Results map as entities. */ 28 | private List data = new ArrayList<>(); 29 | 30 | /** 31 | * Default Constructor. 32 | */ 33 | public PagedResultWrapper() {} 34 | 35 | /** 36 | * Constructor from a RESULT. 37 | * 38 | * @param rs 39 | * result set 40 | * @param mapper 41 | * mapper 42 | */ 43 | public PagedResultWrapper(PagingIterable rs, int pageSize) { 44 | if (null != rs) { 45 | Iterator iterResults = rs.iterator(); 46 | IntStream.range(0, rs.getAvailableWithoutFetching()) 47 | .forEach(item -> data.add(iterResults.next())); 48 | if (null != rs.getExecutionInfo().getPagingState()) { 49 | ByteBuffer pagingState = rs.getExecutionInfo().getPagingState(); 50 | if (pagingState != null && pagingState.hasArray()) { 51 | pageState = Optional.ofNullable(Bytes.toHexString(pagingState)); 52 | } 53 | } 54 | this.pageSize = pageSize; 55 | } 56 | } 57 | 58 | public PagedResultWrapper(MappedAsyncPagingIterable rs, int pageSize) { 59 | if (null != rs) { 60 | rs.currentPage().forEach(data::add); 61 | ByteBuffer pagingState = rs.getExecutionInfo().getPagingState(); 62 | if (pagingState != null && pagingState.hasArray()) { 63 | pageState = Optional.ofNullable(Bytes.toHexString(pagingState)); 64 | } 65 | this.pageSize = pageSize; 66 | } 67 | } 68 | 69 | /** 70 | * Getter accessor for attribute 'pageState'. 71 | * 72 | * @return 73 | * current value of 'pageState' 74 | */ 75 | public Optional getPageState() { 76 | return pageState; 77 | } 78 | 79 | /** 80 | * Getter accessor for attribute 'pageSize'. 81 | * 82 | * @return 83 | * current value of 'pageSize' 84 | */ 85 | public Integer getPageSize() { 86 | return pageSize; 87 | } 88 | 89 | /** 90 | * Getter accessor for attribute 'data'. 91 | * 92 | * @return 93 | * current value of 'data' 94 | */ 95 | public List getData() { 96 | return data; 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/service/AstraService.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.service; 2 | 3 | import java.time.Instant; 4 | import java.time.temporal.ChronoUnit; 5 | import java.util.List; 6 | import java.util.Optional; 7 | import java.util.UUID; 8 | 9 | import javax.annotation.PreDestroy; 10 | 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.stereotype.Component; 14 | 15 | import com.datastax.astra.dao.SessionManager; 16 | import com.datastax.astra.dao.SpacecraftInstrumentsDao; 17 | import com.datastax.astra.dao.SpacecraftJourneyDao; 18 | import com.datastax.astra.dao.SpacecraftMapper; 19 | import com.datastax.astra.dao.SpacecraftMapperBuilder; 20 | import com.datastax.astra.entity.SpacecraftJourneyCatalog; 21 | import com.datastax.astra.entity.SpacecraftLocationOverTime; 22 | import com.datastax.astra.entity.SpacecraftPressureOverTime; 23 | import com.datastax.astra.entity.SpacecraftSpeedOverTime; 24 | import com.datastax.astra.entity.SpacecraftTemperatureOverTime; 25 | import com.datastax.astra.model.PagedResultWrapper; 26 | import com.datastax.oss.driver.api.core.CqlSession; 27 | import com.datastax.oss.driver.api.core.PagingIterable; 28 | import com.datastax.oss.driver.api.core.uuid.Uuids; 29 | 30 | /** 31 | * Implementation of Service for controller 32 | * 33 | */ 34 | @Component 35 | public class AstraService { 36 | 37 | /** Logger for the class. */ 38 | private static final Logger LOGGER = LoggerFactory.getLogger(AstraService.class); 39 | 40 | /** Driver Daos. */ 41 | private SpacecraftJourneyDao spacecraftJourneyDao; 42 | private SpacecraftInstrumentsDao spacecraftInstrumentsDao; 43 | 44 | /** 45 | * Find all spacecrafts in the catalog. 46 | */ 47 | public List< SpacecraftJourneyCatalog > findAllSpacecrafts() { 48 | // no paging we don't expect more than 5k journeys 49 | return getSpaceCraftJourneyDao().findAll().all(); 50 | } 51 | 52 | /** 53 | * Find all journeys for a spacecraft. 54 | * 55 | * @param spacecraftName 56 | * unique spacecraft name (PK) 57 | * @return 58 | * list of journeys 59 | */ 60 | public List < SpacecraftJourneyCatalog > findAllJourneysForSpacecraft(String spacecraftName) { 61 | // no paging we don't expect more than 5k journeys 62 | return getSpaceCraftJourneyDao().findAllJourneysForSpacecraft(spacecraftName).all(); 63 | } 64 | 65 | /** 66 | * Search by primary key, unique record expect. 67 | * 68 | * @param spacecraftName 69 | * unique spacecraft name (PK) 70 | * @param journeyid 71 | * journey unique identifier 72 | * @return 73 | * journey details if it exists 74 | */ 75 | public Optional< SpacecraftJourneyCatalog > findJourneyById(String spacecraftName, UUID journeyId) { 76 | return getSpaceCraftJourneyDao().findById(spacecraftName, journeyId); 77 | } 78 | 79 | /** 80 | * Create a new {@link SpacecraftJourneyCatalog}. 81 | * 82 | * @param spacecraftName 83 | * unique spacecraft name (PK) 84 | * @param summary 85 | * short description 86 | * @return 87 | * generated journey id 88 | */ 89 | public UUID createSpacecraftJourney(String spacecraftName, String summary) { 90 | UUID journeyUid = Uuids.timeBased(); 91 | LOGGER.debug("Creating journey {} for spacecraft {}", journeyUid, spacecraftName); 92 | SpacecraftJourneyCatalog dto = new SpacecraftJourneyCatalog(); 93 | dto.setName(spacecraftName); 94 | dto.setSummary(summary); 95 | dto.setStart(Instant.now()); 96 | dto.setEnd(Instant.now().plus(1000, ChronoUnit.MINUTES)); 97 | dto.setActive(false); 98 | dto.setJourneyId(journeyUid); 99 | getSpaceCraftJourneyDao().upsert(dto); 100 | return journeyUid; 101 | } 102 | 103 | /** 104 | * Retrieve temperature readings for a journey. 105 | * 106 | * @param spacecraftName 107 | * name of spacecrafr 108 | * @param journeyId 109 | * journey identifier 110 | * @param pageSize 111 | * page size 112 | * @param pageState 113 | * page state 114 | * @return 115 | * result page 116 | */ 117 | public PagedResultWrapper getTemperatureReading( 118 | String spacecraftName, UUID journeyId, 119 | Optional pageSize, Optional pageState) { 120 | PagingIterable daoResult = 121 | getSpaceCraftInstrumentsDao().getTemperatureReading(spacecraftName, journeyId, pageSize, pageState); 122 | return new PagedResultWrapper(daoResult, 123 | pageSize.isPresent() ? pageSize.get() : 0); 124 | } 125 | 126 | /** 127 | * Create a new {@link SpacecraftTemperatureOverTime} for each item in the array. 128 | * 129 | * @param readings 130 | * An array unique temperature readings 131 | * @return 132 | * true if successful 133 | */ 134 | public void insertTemperatureReading(SpacecraftTemperatureOverTime[] readings) { 135 | long top = System.currentTimeMillis(); 136 | getSpaceCraftInstrumentsDao().insertTemperatureReadingAsync(readings) 137 | .whenComplete((res,ex) -> LOGGER.debug("{} temperature reading(s) inserted in {} millis", 138 | readings.length, System.currentTimeMillis() - top)); 139 | } 140 | 141 | /** 142 | * Create a new {@link SpacecraftLocationOverTime} for each item in the array. 143 | * 144 | * @param readings 145 | * An array unique location readings 146 | * @return 147 | * true if successful 148 | */ 149 | public void insertLocationReading(SpacecraftLocationOverTime[] readings) { 150 | long top = System.currentTimeMillis(); 151 | getSpaceCraftInstrumentsDao() 152 | .insertLocationReadingAsync(readings) 153 | .whenComplete((res,ex) -> LOGGER.debug("{} location reading(s) inserted in {} millis", 154 | readings.length, System.currentTimeMillis() - top)); 155 | } 156 | 157 | /** 158 | * Create a new {@link SpacecraftPressureOverTime} for each item in the array. 159 | * 160 | * @param readings 161 | * An array unique pressure readings 162 | * @return 163 | * true if successful 164 | */ 165 | public void insertPressureReading(SpacecraftPressureOverTime[] readings) { 166 | long top = System.currentTimeMillis(); 167 | getSpaceCraftInstrumentsDao() 168 | .insertPressureReadingAsync(readings) 169 | .whenComplete((res,ex) -> LOGGER.debug("{} pressure reading(s) inserted in {} millis", 170 | readings.length, System.currentTimeMillis() - top)); 171 | } 172 | 173 | /** 174 | * Create a new {@link SpacecraftSpeedOverTime} for each item in the array. 175 | * 176 | * @param readings 177 | * An array unique pressure readings 178 | * @return 179 | * true if successful 180 | */ 181 | public void insertSpeedReading(SpacecraftSpeedOverTime[] readings) { 182 | long top = System.currentTimeMillis(); 183 | getSpaceCraftInstrumentsDao() 184 | .insertSpeedReadingAsync(readings) 185 | .whenComplete((res,ex) -> LOGGER.debug("{} speed reading(s) inserted in {} millis", 186 | readings.length, System.currentTimeMillis() - top)); 187 | } 188 | 189 | /** 190 | * Retrieve pressure readings for a journey. 191 | * 192 | * @param spacecraftName 193 | * name of spacecrafr 194 | * @param journeyId 195 | * journey identifier 196 | * @param pageSize 197 | * page size 198 | * @param pageState 199 | * page state 200 | * @return 201 | * result page 202 | */ 203 | public PagedResultWrapper getPressureReading( 204 | String spacecraftName, UUID journeyId, 205 | Optional pageSize, Optional pageState) { 206 | PagingIterable daoResult = 207 | getSpaceCraftInstrumentsDao().getPressureReading(spacecraftName, journeyId, pageSize, pageState); 208 | return new PagedResultWrapper(daoResult, 209 | pageSize.isPresent() ? pageSize.get() : 0); 210 | } 211 | 212 | /** 213 | * Retrieve speed readings for a journey. 214 | * 215 | * @param spacecraftName 216 | * name of spacecrafr 217 | * @param journeyId 218 | * journey identifier 219 | * @param pageSize 220 | * page size 221 | * @param pageState 222 | * page state 223 | * @return 224 | * result page 225 | */ 226 | public PagedResultWrapper getSpeedReading( 227 | String spacecraftName, UUID journeyId, 228 | Optional pageSize, Optional pageState) { 229 | PagingIterable daoResult = 230 | getSpaceCraftInstrumentsDao().getSpeedReading(spacecraftName, journeyId, pageSize, pageState); 231 | return new PagedResultWrapper(daoResult, 232 | pageSize.isPresent() ? pageSize.get() : 0); 233 | } 234 | 235 | /** 236 | * Retrieve speed readings for a journey. 237 | * 238 | * @param spacecraftName 239 | * name of spacecrafr 240 | * @param journeyId 241 | * journey identifier 242 | * @param pageSize 243 | * page size 244 | * @param pageState 245 | * page state 246 | * @return 247 | * result page 248 | */ 249 | public PagedResultWrapper getLocationReading( 250 | String spacecraftName, UUID journeyId, 251 | Optional pageSize, Optional pageState) { 252 | PagingIterable daoResult = 253 | getSpaceCraftInstrumentsDao().getLocationReading(spacecraftName, journeyId, pageSize, pageState); 254 | return new PagedResultWrapper(daoResult, 255 | pageSize.isPresent() ? pageSize.get() : 0); 256 | } 257 | 258 | protected synchronized SpacecraftJourneyDao getSpaceCraftJourneyDao() { 259 | if (spacecraftJourneyDao == null) { 260 | CqlSession cqlSession = SessionManager.getInstance().connectToAstra(); 261 | SpacecraftMapper mapper = new SpacecraftMapperBuilder(cqlSession).build(); 262 | this.spacecraftJourneyDao = mapper.spacecraftJourneyDao(cqlSession.getKeyspace().get()); 263 | } 264 | return spacecraftJourneyDao; 265 | } 266 | 267 | protected synchronized SpacecraftInstrumentsDao getSpaceCraftInstrumentsDao() { 268 | if (spacecraftInstrumentsDao == null) { 269 | CqlSession cqlSession = SessionManager.getInstance().connectToAstra(); 270 | SpacecraftMapper mapper = new SpacecraftMapperBuilder(cqlSession).build(); 271 | this.spacecraftInstrumentsDao = mapper.spacecraftInstrumentsDao(cqlSession.getKeyspace().get()); 272 | } 273 | return spacecraftInstrumentsDao; 274 | } 275 | 276 | /** 277 | * Properly close CqlSession 278 | */ 279 | @PreDestroy 280 | public void cleanUp() { 281 | SessionManager.getInstance().close(); 282 | } 283 | 284 | } 285 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/astra/utils/CqlFileUtils.java: -------------------------------------------------------------------------------- 1 | package com.datastax.astra.utils; 2 | 3 | import java.io.FileNotFoundException; 4 | import java.io.InputStream; 5 | import java.util.Arrays; 6 | import java.util.Scanner; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import com.datastax.oss.driver.api.core.CqlSession; 12 | import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; 13 | 14 | /** 15 | * Allow to execute a CQL File 16 | */ 17 | public class CqlFileUtils { 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(CqlFileUtils.class); 20 | 21 | /** Helper for CQL FILE. */ 22 | private static final String UTF8_ENCODING = "UTF-8"; 23 | private static final String NEW_LINE = System.getProperty("line.separator"); 24 | 25 | /** 26 | * Utils method to load a file as String. 27 | * 28 | * @param fileName 29 | * target file Name. 30 | * @return target file content as String 31 | * @throws FileNotFoundException 32 | */ 33 | private static String loadFileAsString(String fileName) 34 | throws FileNotFoundException { 35 | InputStream in = CqlFileUtils.class.getResourceAsStream(fileName); 36 | if (in == null) { 37 | // Fetch absolute classloader path 38 | in = CqlFileUtils.class.getClassLoader().getResourceAsStream(fileName); 39 | } 40 | if (in == null) { 41 | // Thread 42 | in = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName); 43 | } 44 | if (in == null) { 45 | throw new FileNotFoundException("Cannot load file " + fileName + " please check"); 46 | } 47 | Scanner currentScan = null; 48 | StringBuilder strBuilder = new StringBuilder(); 49 | try { 50 | currentScan = new Scanner(in, UTF8_ENCODING); 51 | while (currentScan.hasNextLine()) { 52 | strBuilder.append(currentScan.nextLine()); 53 | strBuilder.append(NEW_LINE); 54 | } 55 | } finally { 56 | if (currentScan != null) { 57 | currentScan.close(); 58 | } 59 | } 60 | return strBuilder.toString(); 61 | } 62 | 63 | /** 64 | * Allows to execute a CQL File. 65 | * 66 | * @param dseSession 67 | * current dse Session 68 | * @param fileName 69 | * cql file name to execute 70 | * @throws FileNotFoundException 71 | * cql file has not been found. 72 | */ 73 | public static void executeCQLFile(CqlSession cqlSession, String fileName) 74 | throws FileNotFoundException { 75 | long top = System.currentTimeMillis(); 76 | Arrays.stream(loadFileAsString(fileName).split(";")).forEach(statement -> { 77 | String query = statement.replaceAll(NEW_LINE, "").trim(); 78 | try { 79 | if (query.length() > 0) { 80 | cqlSession.execute(query); 81 | LOGGER.info(" + Executed. " + query); 82 | } 83 | } catch (InvalidQueryException e) { 84 | LOGGER.warn(" + Query Ignore. " + query, e); 85 | } 86 | }); 87 | LOGGER.info("Execution done in {} millis.", System.currentTimeMillis() - top); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | datastax-java-driver { 2 | 3 | basic { 4 | 5 | # Raising read timeout anticipating low bandwith 6 | request.timeout = 8 seconds 7 | 8 | # Consistency Level by default for Astra 9 | request.consistency = LOCAL_QUORUM 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------- 2 | # Spring Boot Config 3 | # ---------------------------------------------------------- 4 | spring: 5 | application: 6 | name: Getting Started with Astra 7 | jackson: 8 | serialization: 9 | WRITE_DATES_AS_TIMESTAMPS: false 10 | server: 11 | port: 8080 12 | -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ${AnsiColor.RED} ___ _ 2 | ${AnsiColor.RED} / _ \ | | 3 | ${AnsiColor.RED} / /_\ \ ___ | |_ _ __ __ _ 4 | ${AnsiColor.RED} | _ |/ __|| __|| '__| / _` | 5 | ${AnsiColor.RED} | | | |\__ \| |_ | | | (_| | 6 | ${AnsiColor.RED} \_| |_/|___/ \__||_| \__,_| 7 | ${AnsiColor.BLUE} 8 | ${AnsiColor.BLUE} Getting Started with Astra 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} %magenta(%-5level) %cyan(%-45logger) : %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/resources/schema.cql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS spacecraft_journey_catalog ( 2 | spacecraft_name text, 3 | journey_id timeuuid, 4 | start timestamp, 5 | end timestamp, 6 | active boolean, 7 | summary text, 8 | PRIMARY KEY ((spacecraft_name), journey_id) 9 | ) WITH CLUSTERING ORDER BY (journey_id desc); 10 | 11 | CREATE TABLE IF NOT EXISTS spacecraft_speed_over_time ( 12 | spacecraft_name text, 13 | journey_id timeuuid, 14 | speed double, 15 | reading_time timestamp, 16 | speed_unit text, 17 | PRIMARY KEY ((spacecraft_name, journey_id), reading_time) 18 | ) WITH CLUSTERING ORDER BY (reading_time DESC); 19 | 20 | CREATE TABLE IF NOT EXISTS spacecraft_temperature_over_time ( 21 | spacecraft_name text, 22 | journey_id timeuuid, 23 | temperature double, 24 | temperature_unit text, 25 | reading_time timestamp, 26 | PRIMARY KEY ((spacecraft_name, journey_id), reading_time) 27 | ) WITH CLUSTERING ORDER BY (reading_time DESC); 28 | 29 | CREATE TABLE IF NOT EXISTS spacecraft_pressure_over_time ( 30 | spacecraft_name text, 31 | journey_id timeuuid, 32 | pressure double, 33 | pressure_unit text, 34 | reading_time timestamp, 35 | PRIMARY KEY ((spacecraft_name, journey_id), reading_time) 36 | ) WITH CLUSTERING ORDER BY (reading_time DESC); 37 | 38 | CREATE TYPE IF NOT EXISTS location_udt ( 39 | x_coordinate double, 40 | y_coordinate double, 41 | z_coordinate double); 42 | 43 | CREATE TABLE IF NOT EXISTS spacecraft_location_over_time ( 44 | spacecraft_name text, 45 | journey_id timeuuid, 46 | location frozen, 47 | location_unit text, 48 | reading_time timestamp, 49 | PRIMARY KEY ((spacecraft_name, journey_id), reading_time) 50 | ) WITH CLUSTERING ORDER BY (reading_time DESC); 51 | --------------------------------------------------------------------------------