This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new a295bde584 TODO-239: Port utility-beans/HTML5/image demos into 
petstore; drop ATOM/JSON-Schema/SSE.
a295bde584 is described below

commit a295bde5846f05de594395ff553ca5d58f4e512e
Author: James Bognar <[email protected]>
AuthorDate: Wed Jul 15 09:07:49 2026 -0400

    TODO-239: Port utility-beans/HTML5/image demos into petstore; drop 
ATOM/JSON-Schema/SSE.
    
    3 ported into petstore-core with tests + both deployments wired: 
utility-beans
    (PetInfoResource), HTML5 bean-builder (PetHtmlResource), and image 
upload/serve
    (PetPhotoSerializer/PetPhotoParser, now using Pet.photo). 3 consciously 
dropped
    (ATOM-feed, JSON-Schema, SSE) — no petstore-domain tie-in, coverage 
preserved
    elsewhere. Closes the TODO-86 acceptance gap left open by TODO-112.
    
    Co-authored-by: Cursor <[email protected]>
---
 .../juneau/petstore/marshall/PetPhotoParser.java   |  61 ++++++++++++
 .../petstore/marshall/PetPhotoSerializer.java      |  63 ++++++++++++
 .../juneau/petstore/marshall/package-info.java     |  26 +++++
 .../juneau/petstore/rest/PetHtmlResource.java      | 106 +++++++++++++++++++++
 .../juneau/petstore/rest/PetInfoResource.java      |  93 ++++++++++++++++++
 .../juneau/petstore/rest/PetStoreResource.java     |  46 +++++++++
 .../src/main/resources/petstore/init/Pets.json     |   2 +-
 .../juneau/petstore/rest/PetHtmlResource_Test.java |  56 +++++++++++
 .../juneau/petstore/rest/PetInfoResource_Test.java |  58 +++++++++++
 .../petstore/rest/PetStoreResource_Test.java       |  42 ++++++++
 .../juneau/petstore/jetty/RootResources.java       |   2 +
 .../juneau/petstore/springboot/RootResources.java  |   2 +
 12 files changed, 556 insertions(+), 1 deletion(-)

diff --git 
a/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/marshall/PetPhotoParser.java
 
b/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/marshall/PetPhotoParser.java
new file mode 100644
index 0000000000..c3cc43b178
--- /dev/null
+++ 
b/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/marshall/PetPhotoParser.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.petstore.marshall;
+
+import java.awt.image.*;
+import java.io.*;
+
+import javax.imageio.*;
+
+import org.apache.juneau.marshall.*;
+import org.apache.juneau.marshall.parser.*;
+
+/**
+ * Parses {@code image/png}/{@code image/jpg} byte streams into {@link 
BufferedImage} pet photos.
+ *
+ * <p>
+ * Petstore-local port of the deleted {@code juneau-examples-rest} image demo, 
relocated here (rather than
+ * {@code juneau-examples-core}) so {@code juneau-petstore-core} stays free of 
any dependency on the examples
+ * modules.  Wired into {@link 
org.apache.juneau.petstore.rest.PetStoreResource#putPetPhoto(long, 
BufferedImage)}.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/JuneauPetstore";>juneau-petstore</a>
+ * </ul>
+ */
+public class PetPhotoParser extends InputStreamParser {
+
+       /**
+        * Constructor with default settings (consumes image/png and image/jpg).
+        */
+       public PetPhotoParser() {
+               super(create().consumes("image/png,image/jpg"));
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override /* Parser */
+       @SuppressWarnings({
+               "unchecked" // Type erasure requires cast for image return
+       })
+       public <T> T doParse(ParserSession session, ParserPipe pipe, 
ClassMeta<T> type) throws IOException, ParseException {
+               try (var is = pipe.getInputStream()) {
+                       var image = ImageIO.read(is);
+                       return (T)image;
+               }
+       }
+}
diff --git 
a/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/marshall/PetPhotoSerializer.java
 
b/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/marshall/PetPhotoSerializer.java
new file mode 100644
index 0000000000..f2fc5ab0cf
--- /dev/null
+++ 
b/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/marshall/PetPhotoSerializer.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.petstore.marshall;
+
+import java.awt.image.*;
+import java.io.*;
+
+import javax.imageio.*;
+
+import org.apache.juneau.commons.http.*;
+import org.apache.juneau.marshall.serializer.*;
+
+/**
+ * Serializes {@link BufferedImage} pet photos to {@code image/png}/{@code 
image/jpeg} byte streams.
+ *
+ * <p>
+ * Petstore-local port of the deleted {@code juneau-examples-rest} image demo, 
relocated here (rather than
+ * {@code juneau-examples-core}) so {@code juneau-petstore-core} stays free of 
any dependency on the examples
+ * modules.  Wired into {@link 
org.apache.juneau.petstore.rest.PetStoreResource#getPetPhoto(long)}.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/JuneauPetstore";>juneau-petstore</a>
+ * </ul>
+ */
+@SuppressWarnings({
+       "java:S110" // Inheritance depth acceptable for this class hierarchy
+})
+public class PetPhotoSerializer extends OutputStreamSerializer {
+
+       /**
+        * Constructor with default settings (produces image/png and 
image/jpeg).
+        */
+       public PetPhotoSerializer() {
+               super(create().produces("image/png,image/jpeg"));
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void doSerialize(SerializerSession session, SerializerPipe pipe, 
Object o) throws IOException, SerializeException {
+               var image = (RenderedImage)o;
+               MediaType mediaType = session.getMediaType();
+               try (var os = pipe.getOutputStream()) {
+                       // ImageIO format names are subtypes ("png", "jpeg"), 
not MIME types ("image/png").
+                       ImageIO.write(image, mediaType.getSubType(), os);
+               }
+       }
+}
diff --git 
a/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/marshall/package-info.java
 
b/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/marshall/package-info.java
new file mode 100644
index 0000000000..e412106f52
--- /dev/null
+++ 
b/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/marshall/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Petstore-local marshalling support.
+ *
+ * <p>
+ * Custom {@link org.apache.juneau.marshall.serializer.Serializer}/{@link 
org.apache.juneau.marshall.parser.Parser}
+ * implementations that are petstore-specific and therefore live here rather 
than in a shared marshalling module.
+ * Kept dependency-free of {@code juneau-examples-core} so {@code 
juneau-petstore-core} has no example-module
+ * dependency.
+ */
+package org.apache.juneau.petstore.marshall;
diff --git 
a/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/rest/PetHtmlResource.java
 
b/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/rest/PetHtmlResource.java
new file mode 100644
index 0000000000..6a0971af32
--- /dev/null
+++ 
b/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/rest/PetHtmlResource.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.petstore.rest;
+
+import static org.apache.juneau.bean.html5.HtmlBuilder.*;
+
+import java.util.*;
+
+import org.apache.juneau.bean.html5.*;
+import org.apache.juneau.http.*;
+import org.apache.juneau.http.response.*;
+import org.apache.juneau.petstore.dto.*;
+import org.apache.juneau.petstore.service.*;
+import org.apache.juneau.rest.server.*;
+import org.apache.juneau.rest.server.beans.*;
+import org.apache.juneau.rest.server.servlet.*;
+
+/**
+ * Petstore-flavored demo of Juneau's HTML5 bean-builder DSL.
+ *
+ * <p>
+ * Ports the deleted {@code juneau-examples-rest} {@code HtmlBeansResource} 
demo, swapping its generic
+ * div/form/table samples for hand-built HTML5 fragments rendered from live 
petstore data — a "pet card"
+ * {@link Div} and an all-pets {@link Table} — using {@link 
org.apache.juneau.bean.html5.HtmlBuilder} static
+ * factory methods instead of relying on the framework's automatic HTML-doc 
view.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/HtmlBeans";>Using with HTML Beans</a>
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/JuneauPetstore";>juneau-petstore</a>
+ * </ul>
+ */
+@Rest(
+       path="/petstore-html",
+       title="Petstore HTML5 bean builder",
+       description="Hand-built HTML5 div/table fragments rendered from live 
petstore data."
+)
+@SuppressWarnings({
+       "java:S110" // Inheritance depth acceptable for example/demo code
+})
+public class PetHtmlResource extends BasicRestServlet {
+
+       private static final long serialVersionUID = 1L;
+
+       private final transient PetStore store = new PetStore();
+
+       /**
+        * Lists the child endpoints.
+        *
+        * @return Descriptive links to the child endpoints.
+        */
+       @RestGet("/")
+       public ResourceDescriptions getChildDescriptions() {
+               return ResourceDescriptions
+                       .create()
+                       .append("table", "All pets, rendered as a hand-built 
HTML5 table")
+                       .append("card/1", "A single pet, rendered as a 
hand-built HTML5 'pet card' div");
+       }
+
+       /**
+        * Renders a single pet as a hand-built HTML5 "pet card" div.
+        *
+        * @param id The pet ID.
+        * @return A div containing the pet's name, species, price, and status.
+        * @throws NotFound If no pet with the given ID exists.
+        */
+       @RestGet("/card/{id}")
+       public Div getPetCard(@Path("id") long id) {
+               var pet = store.getPet(id);
+               if (pet == null)
+                       throw new NotFound("Pet not found: id={0}", id);
+               return div(
+                       p(b(pet.getName())),
+                       p("Species: " + pet.getSpecies()),
+                       p("Price: $" + pet.getPrice()),
+                       p("Status: " + pet.getStatus())
+               );
+       }
+
+       /**
+        * Renders all pets as a hand-built HTML5 table.
+        *
+        * @return A table with one row per pet (name, species, price, status).
+        */
+       @RestGet("/table")
+       public Table getPetTable() {
+               var rows = new ArrayList<>();
+               rows.add(tr(th("Name"), th("Species"), th("Price"), 
th("Status")));
+               for (var pet : store.getPets())
+                       rows.add(tr(td(pet.getName()), td(pet.getSpecies()), 
td(pet.getPrice()), td(pet.getStatus())));
+               return table(rows.toArray());
+       }
+}
diff --git 
a/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/rest/PetInfoResource.java
 
b/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/rest/PetInfoResource.java
new file mode 100644
index 0000000000..078a2a5dba
--- /dev/null
+++ 
b/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/rest/PetInfoResource.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.petstore.rest;
+
+import org.apache.juneau.petstore.dto.*;
+import org.apache.juneau.rest.server.*;
+import org.apache.juneau.rest.server.beans.*;
+import org.apache.juneau.rest.server.servlet.*;
+
+/**
+ * Petstore-flavored demo of Juneau's REST-server utility beans.
+ *
+ * <p>
+ * Ports the deleted {@code juneau-examples-rest} {@code UtilityBeansResource} 
demo, swapping its generic
+ * {@code Address} sample bean for the petstore domain's {@link Pet} bean.  
Shows {@link BeanDescription},
+ * {@link Hyperlink}, and {@link SeeOtherRoot} — three small utility beans 
intended for use in REST responses
+ * (e.g. {@code OPTIONS} introspection, navigational links, and root-relative 
redirects).
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/UtilityBeans";>Utility Beans</a>
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/JuneauPetstore";>juneau-petstore</a>
+ * </ul>
+ */
+@Rest(
+       path="/petstore-info",
+       title="Petstore utility beans",
+       description="Demonstrates BeanDescription/Hyperlink/SeeOtherRoot 
against the Pet domain bean."
+)
+@SuppressWarnings({
+       "java:S110" // Inheritance depth acceptable for example/demo code
+})
+public class PetInfoResource extends BasicRestServlet {
+
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * Lists the child endpoints.
+        *
+        * @return Descriptive links to the child endpoints.
+        */
+       @RestGet("/")
+       public ResourceDescriptions getChildDescriptions() {
+               return ResourceDescriptions
+                       .create()
+                       .append("BeanDescription", "Example of a 
BeanDescription bean, describing the Pet domain bean")
+                       .append("Hyperlink", "Example of a Hyperlink bean")
+                       .append("SeeOtherRoot", "Example of a SeeOtherRoot 
bean");
+       }
+
+       /**
+        * Describes the petstore {@link Pet} bean's properties.
+        *
+        * @return A {@link BeanDescription} of {@link Pet}.
+        */
+       @RestGet("/BeanDescription")
+       public BeanDescription getPetBeanDescription() {
+               return BeanDescription.of(Pet.class);
+       }
+
+       /**
+        * Returns a hyperlink back to this resource's root.
+        *
+        * @return A hyperlink pointing at {@code /petstore-info}.
+        */
+       @RestGet("/Hyperlink")
+       public Hyperlink getHyperlink() {
+               return Hyperlink.create("/petstore-info", "Back to 
/petstore-info");
+       }
+
+       /**
+        * Redirects to the servlet root.
+        *
+        * @return A redirect to the servlet root.
+        */
+       @RestGet("/SeeOtherRoot")
+       public SeeOtherRoot getSeeOtherRoot() {
+               return SeeOtherRoot.INSTANCE;
+       }
+}
diff --git 
a/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/rest/PetStoreResource.java
 
b/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/rest/PetStoreResource.java
index 7e19fbfa21..8bcb2b6a34 100644
--- 
a/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/rest/PetStoreResource.java
+++ 
b/juneau-petstore/juneau-petstore-core/src/main/java/org/apache/juneau/petstore/rest/PetStoreResource.java
@@ -16,11 +16,14 @@
  */
 package org.apache.juneau.petstore.rest;
 
+import java.awt.image.*;
 import java.util.*;
+import java.util.concurrent.*;
 
 import org.apache.juneau.http.*;
 import org.apache.juneau.http.response.*;
 import org.apache.juneau.petstore.dto.*;
+import org.apache.juneau.petstore.marshall.*;
 import org.apache.juneau.petstore.service.*;
 import org.apache.juneau.rest.server.*;
 import org.apache.juneau.rest.server.servlet.*;
@@ -40,6 +43,8 @@ import org.apache.juneau.rest.server.servlet.*;
  *     <li>{@code POST   /pets}                — create pet
  *     <li>{@code PUT    /pets/{id}}           — update pet
  *     <li>{@code DELETE /pets/{id}}           — delete pet
+ *     <li>{@code GET    /pets/{id}/photo}     — get pet photo
+ *     <li>{@code PUT    /pets/{id}/photo}     — upload pet photo
  *     <li>{@code GET    /orders}              — list orders
  *     <li>{@code GET    /orders/{id}}         — get order
  *     <li>{@code POST   /orders}              — create order
@@ -71,6 +76,9 @@ public class PetStoreResource extends BasicRestServlet {
        /** Backing store.  Singleton servlet → singleton store, shared across 
requests. */
        private final transient PetStore store = new PetStore();
 
+       /** In-memory photo bytes, keyed by pet ID.  Not seeded from JSON — 
populated only via {@link #putPetPhoto}. */
+       private final transient Map<Long,BufferedImage> photos = new 
ConcurrentHashMap<>();
+
        
//------------------------------------------------------------------------------------------------------------------
        // Pets
        
//------------------------------------------------------------------------------------------------------------------
@@ -144,6 +152,44 @@ public class PetStoreResource extends BasicRestServlet {
                }
        }
 
+       /**
+        * Retrieves the uploaded photo for a pet.
+        *
+        * @param id The pet ID.
+        * @return The photo image.
+        * @throws NotFound If no pet with the given ID exists, or no photo has 
been uploaded for it.
+        */
+       @RestGet(path="/pets/{id}/photo", serializers=PetPhotoSerializer.class)
+       public BufferedImage getPetPhoto(@Path("id") long id) {
+               if (store.getPet(id) == null)
+                       throw new NotFound("Pet not found: id={0}", id);
+               var image = photos.get(id);
+               if (image == null)
+                       throw new NotFound("No photo uploaded for pet: id={0}", 
id);
+               return image;
+       }
+
+       /**
+        * Uploads a photo for a pet.
+        *
+        * <p>
+        * On success, also updates the pet's {@link Pet#getPhoto() photo} 
field to point back at this endpoint.
+        *
+        * @param id The pet ID.
+        * @param image The photo image.
+        * @return OK.
+        * @throws NotFound If no pet with the given ID exists.
+        */
+       @RestPut(path="/pets/{id}/photo", parsers=PetPhotoParser.class)
+       public Ok putPetPhoto(@Path("id") long id, @Content BufferedImage 
image) {
+               var pet = store.getPet(id);
+               if (pet == null)
+                       throw new NotFound("Pet not found: id={0}", id);
+               photos.put(id, image);
+               pet.setPhoto("/petstore/pets/" + id + "/photo");
+               return Ok.INSTANCE;
+       }
+
        
//------------------------------------------------------------------------------------------------------------------
        // Orders
        
//------------------------------------------------------------------------------------------------------------------
diff --git 
a/juneau-petstore/juneau-petstore-core/src/main/resources/petstore/init/Pets.json
 
b/juneau-petstore/juneau-petstore-core/src/main/resources/petstore/init/Pets.json
index a78bf19309..4fa3748450 100644
--- 
a/juneau-petstore/juneau-petstore-core/src/main/resources/petstore/init/Pets.json
+++ 
b/juneau-petstore/juneau-petstore-core/src/main/resources/petstore/init/Pets.json
@@ -12,7 +12,7 @@
 // 
***************************************************************************************************************************
 
 [
-       {species:'CAT', name:'Mr. Frisky', price:39.99, tags:['friendly'], 
status:'AVAILABLE', photo:'/petstore/photos/cat'},
+       {species:'CAT', name:'Mr. Frisky', price:39.99, tags:['friendly'], 
status:'AVAILABLE', photo:'/petstore/pets/1/photo'},
        {species:'DOG', name:'Kibbles', price:99.99, tags:['loyal'], 
status:'AVAILABLE'},
        {species:'RABBIT', name:'Hoppy', price:49.99, tags:['friendly','smells 
nice'], status:'AVAILABLE'},
        {species:'RABBIT', name:'Hoppy 2', price:49.99, status:'AVAILABLE'},
diff --git 
a/juneau-petstore/juneau-petstore-core/src/test/java/org/apache/juneau/petstore/rest/PetHtmlResource_Test.java
 
b/juneau-petstore/juneau-petstore-core/src/test/java/org/apache/juneau/petstore/rest/PetHtmlResource_Test.java
new file mode 100644
index 0000000000..adc8af458e
--- /dev/null
+++ 
b/juneau-petstore/juneau-petstore-core/src/test/java/org/apache/juneau/petstore/rest/PetHtmlResource_Test.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.petstore.rest;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.rest.mock.classic.*;
+import org.junit.jupiter.api.*;
+
+@SuppressWarnings({
+       "resource" // Test helpers return Closeables; Eclipse JDT @Owning 
warning is by design.
+})
+class PetHtmlResource_Test extends TestBase {
+
+       private static MockRestClient client() {
+               return MockRestClient.buildJsonLax(PetHtmlResource.class);
+       }
+
+       @Test void a01_getChildDescriptions_listsCardAndTable() throws 
Exception {
+               var content = 
client().get("/").run().assertStatus(200).getContent().asString();
+               assertTrue(content.contains("table"));
+               assertTrue(content.contains("card/1"));
+       }
+
+       @Test void a02_getPetCard_rendersSeededPet() throws Exception {
+               var content = 
client().get("/card/1").run().assertStatus(200).getContent().asString();
+               assertTrue(content.contains("Mr. Frisky"));
+               assertTrue(content.contains("CAT"));
+       }
+
+       @Test void a03_getPetCard_unknownId_404() throws Exception {
+               client().get("/card/99999").run().assertStatus(404);
+       }
+
+       @Test void a04_getPetTable_rendersAllSeededPets() throws Exception {
+               var content = 
client().get("/table").run().assertStatus(200).getContent().asString();
+               assertTrue(content.contains("Name"));
+               assertTrue(content.contains("Mr. Frisky"));
+               assertTrue(content.contains("Kibbles"));
+       }
+}
diff --git 
a/juneau-petstore/juneau-petstore-core/src/test/java/org/apache/juneau/petstore/rest/PetInfoResource_Test.java
 
b/juneau-petstore/juneau-petstore-core/src/test/java/org/apache/juneau/petstore/rest/PetInfoResource_Test.java
new file mode 100644
index 0000000000..844dd756a3
--- /dev/null
+++ 
b/juneau-petstore/juneau-petstore-core/src/test/java/org/apache/juneau/petstore/rest/PetInfoResource_Test.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.petstore.rest;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.petstore.dto.*;
+import org.apache.juneau.rest.mock.classic.*;
+import org.junit.jupiter.api.*;
+
+@SuppressWarnings({
+       "resource" // Test helpers return Closeables; Eclipse JDT @Owning 
warning is by design.
+})
+class PetInfoResource_Test extends TestBase {
+
+       private static MockRestClient client() {
+               return MockRestClient.buildJsonLax(PetInfoResource.class);
+       }
+
+       @Test void a01_getChildDescriptions_listsAllThree() throws Exception {
+               var content = 
client().get("/").run().assertStatus(200).getContent().asString();
+               assertTrue(content.contains("BeanDescription"));
+               assertTrue(content.contains("Hyperlink"));
+               assertTrue(content.contains("SeeOtherRoot"));
+       }
+
+       @Test void a02_getPetBeanDescription_describesPet() throws Exception {
+               var content = 
client().get("/BeanDescription").run().assertStatus(200).getContent().asString();
+               assertTrue(content.contains(Pet.class.getName()));
+               assertTrue(content.contains("species"));
+               assertTrue(content.contains("photo"));
+       }
+
+       @Test void a03_getHyperlink_pointsAtSelf() throws Exception {
+               var content = 
client().get("/Hyperlink").run().assertStatus(200).getContent().asString();
+               assertTrue(content.contains("/petstore-info"));
+       }
+
+       @Test void a04_getSeeOtherRoot_redirectsToRoot() throws Exception {
+               var noRedirect = 
MockRestClient.create(PetInfoResource.class).disableRedirectHandling().ignoreErrors().build();
+               noRedirect.get("/SeeOtherRoot").run().assertStatus(303);
+       }
+}
diff --git 
a/juneau-petstore/juneau-petstore-core/src/test/java/org/apache/juneau/petstore/rest/PetStoreResource_Test.java
 
b/juneau-petstore/juneau-petstore-core/src/test/java/org/apache/juneau/petstore/rest/PetStoreResource_Test.java
index 4ed32062c5..47ad04bf0b 100644
--- 
a/juneau-petstore/juneau-petstore-core/src/test/java/org/apache/juneau/petstore/rest/PetStoreResource_Test.java
+++ 
b/juneau-petstore/juneau-petstore-core/src/test/java/org/apache/juneau/petstore/rest/PetStoreResource_Test.java
@@ -19,6 +19,11 @@ package org.apache.juneau.petstore.rest;
 import static org.apache.juneau.test.bct.BctAssertions.*;
 import static org.junit.jupiter.api.Assertions.*;
 
+import java.awt.image.*;
+import java.io.*;
+
+import javax.imageio.*;
+
 import org.apache.juneau.*;
 import org.apache.juneau.petstore.dto.*;
 import org.apache.juneau.petstore.dto.Order;
@@ -143,4 +148,41 @@ class PetStoreResource_Test extends TestBase {
                c.delete("/users/dvaughn").run().assertStatus(200);
                c.get("/users/dvaughn").run().assertStatus(404);
        }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // d — pet photos
+       
//------------------------------------------------------------------------------------------------------------------
+
+       private static byte[] pngBytes() throws IOException {
+               var image = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB);
+               try (var baos = new ByteArrayOutputStream()) {
+                       ImageIO.write(image, "png", baos);
+                       return baos.toByteArray();
+               }
+       }
+
+       @Test void d01_getPetPhoto_notUploaded_404() throws Exception {
+               
client().get("/pets/1/photo").accept("image/png").run().assertStatus(404);
+       }
+
+       @Test void d02_getPetPhoto_unknownPet_404() throws Exception {
+               
client().get("/pets/99999/photo").accept("image/png").run().assertStatus(404);
+       }
+
+       @Test void d03_putPetPhoto_unknownPet_404() throws Exception {
+               client().put("/pets/99999/photo", new 
ByteArrayInputStream(pngBytes())).contentType("image/png")
+                       .run().assertStatus(404);
+       }
+
+       @Test void d04_putPetPhoto_thenGet_roundTrip() throws Exception {
+               var c = client();
+               c.put("/pets/1/photo", new 
ByteArrayInputStream(pngBytes())).contentType("image/png")
+                       .run().assertStatus(200);
+
+               var bytes = 
c.get("/pets/1/photo").accept("image/png").run().assertStatus(200).getContent().asBytes();
+               assertTrue(bytes.length > 0);
+
+               var pet = 
c.get("/pets/1").run().assertStatus(200).getContent().as(Pet.class);
+               assertEquals("/petstore/pets/1/photo", pet.getPhoto());
+       }
 }
diff --git 
a/juneau-petstore/juneau-petstore-jetty/src/main/java/org/apache/juneau/petstore/jetty/RootResources.java
 
b/juneau-petstore/juneau-petstore-jetty/src/main/java/org/apache/juneau/petstore/jetty/RootResources.java
index aaead2cce7..9993ba71de 100644
--- 
a/juneau-petstore/juneau-petstore-jetty/src/main/java/org/apache/juneau/petstore/jetty/RootResources.java
+++ 
b/juneau-petstore/juneau-petstore-jetty/src/main/java/org/apache/juneau/petstore/jetty/RootResources.java
@@ -48,6 +48,8 @@ import org.apache.juneau.rest.server.widget.*;
                PetMustacheViewResource.class,
                PetFreemarkerViewResource.class,
                PetstoreUiResource.class,
+               PetInfoResource.class,
+               PetHtmlResource.class,
                ConfigResource.class,
                LogsResource.class,
                ShutdownResource.class
diff --git 
a/juneau-petstore/juneau-petstore-springboot/src/main/java/org/apache/juneau/petstore/springboot/RootResources.java
 
b/juneau-petstore/juneau-petstore-springboot/src/main/java/org/apache/juneau/petstore/springboot/RootResources.java
index dab3c7ca79..fb6a760358 100644
--- 
a/juneau-petstore/juneau-petstore-springboot/src/main/java/org/apache/juneau/petstore/springboot/RootResources.java
+++ 
b/juneau-petstore/juneau-petstore-springboot/src/main/java/org/apache/juneau/petstore/springboot/RootResources.java
@@ -54,6 +54,8 @@ import org.apache.juneau.rest.server.widget.*;
                PetMustacheViewResource.class,
                PetFreemarkerViewResource.class,
                PetstoreUiResource.class,
+               PetInfoResource.class,
+               PetHtmlResource.class,
                HelloResource.class
        }
 )

Reply via email to