Zobrazují se příspěvky se štítkemJava. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemJava. Zobrazit všechny příspěvky

pátek 21. července 2023

Použítí oneOf v YAML + openapi generator

 Definice problému

Mám Spring Boot aplikaci REST JSON služby. Služba má definované API pomocí YAML. Z YAML souboru jsou generovány Java třídy pomocí openapi-generator-maven-plugin verze 6.6.0 nebo novější.

Pokud je v YAML

Addressee:
oneOf:
- $ref: '#/components/schemas/AddresseeBranch'
- $ref: '#/components/schemas/AddresseeHome'

Je při generování vytvořeno rozhraní Addressee a třídy AddresseeBranch a AddresseeHome implementující rozhraní Addressee.
Při volání služby a následném pokusu o deserializaci nastane chyba

Cannot construct instance of `...Akddressee` (no Creators, like default constructor, exist).

Ta je způsobena tím, že deserializer neví na kterou instanci mapovat. Použije proto (nevhodně) samotné rozhraní, které však nelze instanciovat.
Tento problém je na githubu ve stavu otevřený.

Řešení

V některých případech (kdy je jedna z implementací pouhým rozšířením druhé) je možné pomocí SimpleAbstractTypeResolver napevno definovat na kterou z uvedených instancí se mají příchozí data deserializovat. Takové řešení je popsáno zde.

Obecné řešení však spočívá v použití diskriminátoru v YAML definici:

Addressee:
oneOf:
- $ref: '#/components/schemas/AddresseeBranch'
- $ref: '#/components/schemas/AddresseeHome'
discriminator:
propertyName: objectType

Následně je třeba v každém z referencovaných objektů definovat property objectType typu String. Ta by měla mít hodnotu odpovídající názvu reference (tj. názvu implementace rozhraní). Klient tak v requestu vlastně sám uvede, který z možných objektů posílá. objectType zároveň musí být povinná. Příklad pro první z uvedených instancí:

AddresseeBranch:
description: Office
type: object
required:
- objectType
properties:
objectType:
type: string
pattern: '^AddresseeBranch$'

Uvádět pattern není povinné. Je však vhodné ho definovat, aby v případě chybné hodnoty zadané klientem nevznikla chyba během zpracování. Takto bude případná chyba odchycena již při validaci requestu.

Pokud chceme aby hodnota property objectType neodpovídala názvu objektu, je třeba definovat mapování mezi hodnotou a názvem objektu. Podrobněji v sekci Mapping Type Names.




pátek 16. listopadu 2012

Několik tipů pro MyBatis 3

Ač je uživatelská příručka MyBatis 3 napsaná přehledně, lze se setkat s některými problémy či možnostmi, které jsou v ní uvedeny pouze okrajově nebo vůbec.
  1. Použil jsem připojení k databázi Sybase v které jsem volal proceduru. Na vstupu procedury byl parametr typu int. Mapování v xml vypadalo následovně:
    <select id="getPostOffice" statementType="CALLABLE" parameterType="biz.prodejna.pasport.PostRequest" resultType="java.lang.Integer">  
        {call search_post (#{psc})}  
    </select>
    psc bylo typu Integer. Mapoval jsem tedy java.lang.Integer z javy na int z databáze. Výsledkem byla následující chyba:
     Cause: java.sql.SQLException: JZ006: Caught IOException: java.io.IOException: JZ0SL: Unsupported SQL type 1111.  
     ; SQL []; JZ006: Caught IOException: java.io.IOException: JZ0SL: Unsupported SQL type 1111.; nested exception is java.sql.SQLException: JZ006: Caught IOException: java.io.IOException: JZ0SL: Unsupported SQL type 1111.  
    
    Řešením je explicitní uvedení databázového typu pomocí jdbcType:
     <select id="getPostOffice" statementType="CALLABLE" parameterType="biz.prodejna.pasport.PostRequest" resultType="java.lang.Integer">   
       {call search_post (#{psc,jdbcType=INTEGER})}   
     </select>  
    

    Pozor na přebytečné mezery. Pokud budou mezery např. kolem znaku "=" nebo za čárkou, vznikne chyba
     org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.builder.BuilderException: Improper inline parameter map format. Should be: #{propName,attr1=val1,attr2=val2}  
    

  2. Často se volá procedura/select, jejíž výsledek je třeba namapovat na třídu. Názvy členských proměnných třídy se však vždy neshodují s názvy sloupců jež jsou výstupem z databáze. K tomu účelu je v MyBatis anotace @Results. Zde je ukázka použití, kde místo předchozího mapování v xml je použita anotace v rozhranní mapperu:
     @Select("{call psc_detail_post (#{psc})}")  
       @Results(value = {  
         @Result(property = "psc"),  
         @Result(property = "premisesName", column = "nazev_provozovny"),  
         @Result(property = "municipality", column = "obec"),  
         @Result(property = "municipalityPart", column = "cast_obce"),  
         @Result(property = "street", column = "ulice"),  
         @Result(property = "descriptiveNumber", column = "cislo_popisne"),  
         @Result(property = "descriptiveCharacter", column = "cislo_popisne_znak"),  
         @Result(property = "landmarkNumber", column = "cislo_orientacni"),  
         @Result(property = "landmarkCharacter", column = "cislo_orientacni_znak"),  
         @Result(property = "email"),  
         @Result(property = "phone", column = "telefon")})  
       biz.prodejna.pasport.PostResponse getDetailPost(Integer psc);  
    

    Zde stojí za povšimnutí parametry psc a email. Jelikož se shodují názvy členských proměnných a výstupních sloupců, není třeba uvádět parametr column.

čtvrtek 31. května 2012

RESTful webová služba 1. díl

  1. Nainstaluji si server glassfish. Ten je možné zdarma získat např. zde. Stačit vám bude ale i Tomcat nebo jiný servletový kontejner.
  2. Vytvořím si v Netbeans novou webovou aplikaci:
  3. V tomto okně je třeba dát Add a následně vybrat nainstalovaný servletový kontejner.
  4. Nyní je třeba nakonfigurovat aplikaci. To je možné dvěma způsoby:

    1.  Prostřednictvím souboru web.xml
    2.  <?xml version="1.0" encoding="UTF-8"?>  
       <web-app xmlns="http://java.sun.com/xml/ns/javaee"  
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
            xsi:schemaLocation="http://java.sun.com/xml/ns/javaee  
       http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">  
         <servlet>  
           <servlet-name>JerseyServlet</servlet-name>  
           <servlet-class>  
             com.sun.jersey.spi.container.servlet.ServletContainer  
           </servlet-class>  
           <load-on-startup>1</load-on-startup>  
         </servlet>  
         <servlet-mapping>  
           <servlet-name>JerseyServlet</servlet-name>  
           <url-pattern>/resources/*</url-pattern>  
         </servlet-mapping>  
       </web-app>  
      

    3. Pomocí anotace @ApplicationPath
    4. 1:  package biz.prodejna.examples.rest;  
      2:    
      3:  import javax.ws.rs.ApplicationPath;  
      4:  import javax.ws.rs.core.Application;  
      5:    
      6:  @ApplicationPath("resources")  
      7:  public class JaxRsConfig extends Application {  
      8:  }  
      

  5. A nyní konečně mohu napsat nějakou svoji třídu.
  6. RESTful webovou službu mohu volat jako bezparametrickou nebo s parametry. V druhém případě mám v podstatě 3 možnosti, jak parametry vkládat:
    • Jako součást URL. Příklad: http://localhost:8080/example-rest/resources/names/jmeno/prijmeni
    • Jako parametry metody GET. Příklad: http://localhost:8080/example-rest/resources/names?jmeno=Adam&prijmeni=Oliva
    • V těle HTTP requestu. Zde mohou být parametry naformátovány v libovolném MIME typu.

    V tomto bodě ukáži pouze první případ. Další případy si nechám do některého z dalších dílů.

    1:  package biz.prodejna.examples.rest;  
    2:    
    3:  import javax.ws.rs.GET;  
    4:  import javax.ws.rs.Path;  
    5:  import javax.ws.rs.PathParam;  
    6:  import javax.ws.rs.Produces;  
    7:    
    8:  @Path("names")  
    9:  public class Names {  
    10:    
    11:    @GET  
    12:    @Path("{first}/{last}")  
    13:    @Produces("text/plain")  
    14:    public String getFullName(  
    15:        @PathParam("first") String firstName, @PathParam("last") String lastName) {  
    16:      return firstName + " " + lastName;  
    17:    }  
    18:  }  
    

    Vysvětlím jednotlivé anotace:
    @Path u třídy udává, pod jakou URL bude služba dostupná.
    @GET definuje, jaká bude použita HTTP metoda.
    @Path u metody definuje, jaké bude pořadí parametrů. Nebo přesněji: jaký tvar bude mít zbytek URL.
    @Produces udává MIME typ v těle HTTP odpovědi.
    @PathParam mapuje parametry metody na části v anotaci @Path

  7. Nyní je už možné aplikaci deployovat na server a zkusit ji zavolat např. z webového prohlížeče: http://localhost:8080/example-rest/resources/names/Adam/Oliva

  8. Proč URL vypadá právě takhle? Vysvětlím:
    example-rest -název aplikace
    resources -definováno ve web.xml nebo anotací @ApplicationPath
    names -definováno anotací třídy @Path
Pokud máte zájem o pokračování tohoto blogu, informujte mě o tom. Co lze očekávat příště:
  • služba vracející odpověď jako xml
  • služba očekávající parametry jako parametry URL
  • tvorba klienta s využitím implementace Jersey.
  • služba očekávající na vstupu XML dokument.

čtvrtek 8. března 2012

EJB jar včetně závislostí

Potřeboval jsem vytvořit EJB jar v němž měly být obsaženy také všechny závislosti. V mém případě MyBatis a Spring Framework. Pro buildování používám maven. Do výsledného jaru samozřejmě nelze zabalit přímo další jary, je třeba je nejprv rozbalit. To vyřešil mavenovský plugin maven-dependency-plugin přidaný do pom.xml. Zajímavá je v tomto místě především jeho část execution:
 <execution>  
   <id>unpack-dependencies</id>  
   <phase>process-classes</phase>  
   <goals>  
     <goal>unpack-dependencies</goal>  
   </goals>  
   <configuration>  
     <!--Definuji co se ma rozbalit do vysledneho jaru-->  
     <includeGroupIds>org.springframework,org.mybatis</includeGroupIds>  
     <outputDirectory>${project.build.directory}/classes</outputDirectory>  
   </configuration>  
 </execution>  
Aby byly přibalené závislosti také v classpath, vypadala definice pluginu maven-ejb-plugin následovně:
 <plugin>  
   <groupId>org.apache.maven.plugins</groupId>  
   <artifactId>maven-ejb-plugin</artifactId>  
   <version>2.3</version>  
   <configuration>  
     <archive>  
       <manifest>  
         <addClasspath>true</addClasspath>  
       </manifest>  
     </archive>  
     <ejbVersion>3.1</ejbVersion>  
   </configuration>  
 </plugin>  
Výsledkem celého snažení ovšem byla nicneříkající chyba:
Caused by: org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 9 in XML document from URL [file:.../META-INF/applicationContext.xml] is invalid; nested exception is org.xml.sax.SAXParseException: cvc-elt.1: Cannot find the declaration of element 'beans'.
Naštěstí jsem narazil na blogspot, který tento problém řeší následovně: "stačí" ze všech použitých springových jarů vybrat soubory META-INF/spring.handlers a META-INF/spring.schemas a sloučit je pouze do dvou. Tyto dva dát do adresáře META-INF a celé to zabalit do jaru. Výsledný jar, pak umístit do lib aplikačního serveru. V případě použití novější verze springu může být třeba tyto soubory doeditovat.