001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.petstore.rest; 018 019import java.awt.image.*; 020import java.util.*; 021import java.util.concurrent.*; 022 023import org.apache.juneau.commons.inject.Bean; 024import org.apache.juneau.http.*; 025import org.apache.juneau.http.response.*; 026import org.apache.juneau.petstore.dto.*; 027import org.apache.juneau.petstore.marshall.*; 028import org.apache.juneau.petstore.service.*; 029import org.apache.juneau.rest.server.*; 030import org.apache.juneau.rest.server.servlet.*; 031 032/** 033 * Petstore CRUD REST resource. 034 * 035 * <p> 036 * Deployment-agnostic. Mounted by both {@code juneau-petstore-jetty} and {@code juneau-petstore-springboot} via 037 * the {@code @Rest(children=...)} of their root router. 038 * 039 * <p> 040 * Endpoints: 041 * <ul> 042 * <li>{@code GET /pets} — list pets 043 * <li>{@code GET /pets/{id}} — get pet 044 * <li>{@code POST /pets} — create pet 045 * <li>{@code PUT /pets/{id}} — update pet 046 * <li>{@code DELETE /pets/{id}} — delete pet 047 * <li>{@code GET /pets/{id}/photo} — get pet photo 048 * <li>{@code PUT /pets/{id}/photo} — upload pet photo 049 * <li>{@code GET /orders} — list orders 050 * <li>{@code GET /orders/{id}} — get order 051 * <li>{@code POST /orders} — create order 052 * <li>{@code PUT /orders/{id}} — update order 053 * <li>{@code DELETE /orders/{id}} — delete order 054 * <li>{@code GET /users} — list users 055 * <li>{@code GET /users/{username}} — get user 056 * <li>{@code POST /users} — create user 057 * <li>{@code PUT /users/{username}} — update user 058 * <li>{@code DELETE /users/{username}} — delete user 059 * </ul> 060 * 061 * <h5 class='section'>See Also:</h5><ul> 062 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauPetstore">juneau-petstore</a> 063 * </ul> 064 */ 065@Rest( 066 path="/petstore", 067 title="Petstore", 068 description="Apache Juneau petstore sample resource.", 069 swagger=@Swagger( 070 tags={ 071 @Tag(name="pet", description="Everything about your pets"), 072 @Tag(name="store", description="Access to petstore orders"), 073 @Tag(name="user", description="Operations about users") 074 } 075 ) 076) 077@SuppressWarnings({ 078 "java:S110" // Inheritance depth acceptable for example/demo code 079}) 080public class PetStoreResource extends BasicRestServlet { 081 082 private static final long serialVersionUID = 1L; 083 084 /** 085 * The shared store, injected from the parent's bean store (the runner registers one seeded {@code PetStore}). 086 * When this resource is mounted standalone (unit tests), {@link #store()} falls back to a classic-rows store. 087 */ 088 @SuppressWarnings({ 089 "java:S2226" // the field is set by the framework's @Bean injection after construction, so it cannot be final. 090 }) 091 @Bean 092 private transient PetStore store; 093 094 @SuppressWarnings({ 095 "java:S2654" // synchronized guards the lazy standalone fallback, racing against the injected value. 096 }) 097 private synchronized PetStore store() { 098 if (store == null) 099 store = new PetStore(); 100 return store; 101 } 102 103 /** In-memory photo bytes, keyed by pet ID. Not seeded from JSON — populated only via {@link #putPetPhoto}. */ 104 private final transient Map<Long,BufferedImage> photos = new ConcurrentHashMap<>(); 105 106 //------------------------------------------------------------------------------------------------------------------ 107 // Pets 108 //------------------------------------------------------------------------------------------------------------------ 109 110 /** 111 * Lists all pets. 112 * 113 * @return All pets. 114 */ 115 @RestGet(path="/pets", swagger=@OpSwagger(tags="pet")) 116 public Collection<Pet> getPets() { 117 return store().getPets(); 118 } 119 120 /** 121 * Retrieves a pet by ID. 122 * 123 * @param id The pet ID. 124 * @return The pet. 125 * @throws NotFound If no pet with the given ID exists. 126 */ 127 @RestGet(path="/pets/{id}", swagger=@OpSwagger(tags="pet")) 128 public Pet getPet(@Path("id") long id) { 129 var pet = store().getPet(id); 130 if (pet == null) 131 throw new NotFound("Pet not found: id=%s", id); 132 return pet; 133 } 134 135 /** 136 * Creates a new pet. 137 * 138 * @param pet The pet to create. 139 * @return The created pet (with assigned ID). 140 */ 141 @RestPost(path="/pets", swagger=@OpSwagger(tags="pet")) 142 public Pet createPet(@Content Pet pet) { 143 return store().createPet(pet); 144 } 145 146 /** 147 * Updates an existing pet. 148 * 149 * @param id The pet ID. 150 * @param pet The replacement pet body. 151 * @return The updated pet. 152 * @throws NotFound If no pet with the given ID exists. 153 */ 154 @RestPut(path="/pets/{id}", swagger=@OpSwagger(tags="pet")) 155 public Pet updatePet(@Path("id") long id, @Content Pet pet) { 156 pet.setId(id); 157 try { 158 return store().updatePet(pet); 159 } catch (PetstoreNotFoundException e) { 160 throw new NotFound(e.getMessage()); 161 } 162 } 163 164 /** 165 * Deletes a pet by ID. 166 * 167 * @param id The pet ID. 168 * @throws NotFound If no pet with the given ID exists. 169 */ 170 @RestDelete(path="/pets/{id}", swagger=@OpSwagger(tags="pet")) 171 public void deletePet(@Path("id") long id) { 172 try { 173 store().deletePet(id); 174 } catch (PetstoreNotFoundException e) { 175 throw new NotFound(e.getMessage()); 176 } 177 } 178 179 /** 180 * Retrieves the uploaded photo for a pet. 181 * 182 * @param id The pet ID. 183 * @return The photo image. 184 * @throws NotFound If no pet with the given ID exists, or no photo has been uploaded for it. 185 */ 186 @RestGet(path="/pets/{id}/photo", serializers=PetPhotoSerializer.class, swagger=@OpSwagger(tags="pet")) 187 public BufferedImage getPetPhoto(@Path("id") long id) { 188 if (store().getPet(id) == null) 189 throw new NotFound("Pet not found: id=%s", id); 190 var image = photos.get(id); 191 if (image == null) 192 throw new NotFound("No photo uploaded for pet: id=%s", id); 193 return image; 194 } 195 196 /** 197 * Uploads a photo for a pet. 198 * 199 * <p> 200 * On success, also updates the pet's {@link Pet#getPhoto() photo} field to point back at this endpoint. 201 * 202 * @param id The pet ID. 203 * @param image The photo image. 204 * @return OK. 205 * @throws NotFound If no pet with the given ID exists. 206 */ 207 @RestPut(path="/pets/{id}/photo", parsers=PetPhotoParser.class, swagger=@OpSwagger(tags="pet")) 208 public Ok putPetPhoto(@Path("id") long id, @Content BufferedImage image) { 209 var pet = store().getPet(id); 210 if (pet == null) 211 throw new NotFound("Pet not found: id=%s", id); 212 photos.put(id, image); 213 pet.setPhoto("/petstore/pets/" + id + "/photo"); 214 return Ok.INSTANCE; 215 } 216 217 //------------------------------------------------------------------------------------------------------------------ 218 // Orders 219 //------------------------------------------------------------------------------------------------------------------ 220 221 /** 222 * Lists all orders. 223 * 224 * @return All orders. 225 */ 226 @RestGet(path="/orders", swagger=@OpSwagger(tags="store")) 227 public Collection<Order> getOrders() { 228 return store().getOrders(); 229 } 230 231 /** 232 * Retrieves an order by ID. 233 * 234 * @param id The order ID. 235 * @return The order. 236 * @throws NotFound If no order with the given ID exists. 237 */ 238 @RestGet(path="/orders/{id}", swagger=@OpSwagger(tags="store")) 239 public Order getOrder(@Path("id") long id) { 240 var order = store().getOrder(id); 241 if (order == null) 242 throw new NotFound("Order not found: id=%s", id); 243 return order; 244 } 245 246 /** 247 * Creates a new order. 248 * 249 * @param order The order to create. 250 * @return The created order (with assigned ID). 251 */ 252 @RestPost(path="/orders", swagger=@OpSwagger(tags="store")) 253 public Order createOrder(@Content Order order) { 254 return store().createOrder(order); 255 } 256 257 /** 258 * Updates an existing order. 259 * 260 * @param id The order ID. 261 * @param order The replacement order body. 262 * @return The updated order. 263 * @throws NotFound If no order with the given ID exists. 264 */ 265 @RestPut(path="/orders/{id}", swagger=@OpSwagger(tags="store")) 266 public Order updateOrder(@Path("id") long id, @Content Order order) { 267 order.setId(id); 268 try { 269 return store().updateOrder(order); 270 } catch (PetstoreNotFoundException e) { 271 throw new NotFound(e.getMessage()); 272 } 273 } 274 275 /** 276 * Deletes an order by ID. 277 * 278 * @param id The order ID. 279 * @throws NotFound If no order with the given ID exists. 280 */ 281 @RestDelete(path="/orders/{id}", swagger=@OpSwagger(tags="store")) 282 public void deleteOrder(@Path("id") long id) { 283 try { 284 store().deleteOrder(id); 285 } catch (PetstoreNotFoundException e) { 286 throw new NotFound(e.getMessage()); 287 } 288 } 289 290 //------------------------------------------------------------------------------------------------------------------ 291 // Users 292 //------------------------------------------------------------------------------------------------------------------ 293 294 /** 295 * Lists all users. 296 * 297 * @return All users. 298 */ 299 @RestGet(path="/users", swagger=@OpSwagger(tags="user")) 300 public Collection<User> getUsers() { 301 return store().getUsers(); 302 } 303 304 /** 305 * Retrieves a user by username. 306 * 307 * @param username The username. 308 * @return The user. 309 * @throws NotFound If no user with the given username exists. 310 */ 311 @RestGet(path="/users/{username}", swagger=@OpSwagger(tags="user")) 312 public User getUser(@Path("username") String username) { 313 var user = store().getUser(username); 314 if (user == null) 315 throw new NotFound("User not found: username=%s", username); 316 return user; 317 } 318 319 /** 320 * Creates a new user. 321 * 322 * @param user The user to create. Must carry a non-null username not already in use. 323 * @return The created user. 324 */ 325 @RestPost(path="/users", swagger=@OpSwagger(tags="user")) 326 public User createUser(@Content User user) { 327 return store().createUser(user); 328 } 329 330 /** 331 * Updates an existing user. 332 * 333 * <p> 334 * The body replaces the user, except that the write-only password is kept when the body has none (or a blank one), 335 * so a GET followed by a PUT of the same body does not erase it. 336 * 337 * @param username The username. 338 * @param user The replacement user body. 339 * @return The updated user. 340 * @throws NotFound If no user with the given username exists. 341 */ 342 @RestPut(path="/users/{username}", swagger=@OpSwagger(tags="user")) 343 public User updateUser(@Path("username") String username, @Content User user) { 344 user.setUsername(username); 345 try { 346 return store().updateUser(user); 347 } catch (PetstoreNotFoundException e) { 348 throw new NotFound(e.getMessage()); 349 } 350 } 351 352 /** 353 * Deletes a user by username. 354 * 355 * @param username The username. 356 * @throws NotFound If no user with the given username exists. 357 */ 358 @RestDelete(path="/users/{username}", swagger=@OpSwagger(tags="user")) 359 public void deleteUser(@Path("username") String username) { 360 try { 361 store().deleteUser(username); 362 } catch (PetstoreNotFoundException e) { 363 throw new NotFound(e.getMessage()); 364 } 365 } 366}