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}