niedziela, 1 lutego 2009

Dramat w czterech aktach - WebService (na szczęście z happyendem)

Jako dopełnienie serii postów związanych z technologiami JEE zaprezentuje wam przykładowe użycie WebSerwisów, wszystko oczywiście przy wykorzystaniu JBoss'a 5.0. Zaczynamy

Akt I

W przykładzie zostanie użyta prosta, lekko okrojona encja z mojego projektu zaliczeniowego.

package org.gen2.biblioteka.encje;

@Entity
public class Autor implements Serializable {


private static final long serialVersionUID = 1L;

private int id;
private String imie;
private String nazwisko;

public Autor() {
super();
}

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getImie() {
return imie;
}

public void setImie(String imie) {
this.imie = imie;
}

public String getNazwisko() {
return nazwisko;
}

public void setNazwisko(String nazwisko) {
this.nazwisko = nazwisko;
}

public String toString() {
return imie + " " + nazwisko;
}
}


Akt II

Definiujemy prosty interfejs (z jedna metoda) dla bean'a który będzie naszym WebSerwisem (tudzież Usługą Sieciową)


package org.gen2.biblioteka.beany;

@WebService
@SOAPBinding(style = Style.DOCUMENT)
public interface AutorWSInterface {
ArrayList pobierzAutora(int idAutora);
}


@WebService - oznacza, ze intefejs będzie służył do implementacji WebSerwisu
@SOAPBinding - definiuje sposób komunikacji pomiędzy 'klientem' a 'serwerem', nie zaleca sie mieszanie style = RPC z use = LITERAL (JBoss w wersji 5.0 nie obsługuje tego jeszcze poprawnie)

Akt III

A teraz sama implemetacja bean'a.

package org.gen2.biblioteka.beany;

@Stateless
@WebService(endpointInterface = "org.gen2.biblioteka.beany.AutorWSInterface")
@SOAPBinding(style = Style.DOCUMENT)
public class AutorWSBean {

private @PersistenceContext(unitName="BibliotekaEJB")
EntityManager em;

@WebMethod
public ArrayList pobierzAutora(int idAutora) {
Query query = em.createQuery("select a from Autor as a where a.id = " + idAutora);
ArrayList autor = new ArrayList();
Autor temp = (Autor) query.getSingleResult();

autor.add(temp.getImie());
autor.add(temp.getNazwisko());

return autor;
}
}


@WebService(endpointInterface = "org.gen2.biblioteka.beany.AutorWSInterface") - musi wskazywać na intefejs który klasa ma implementować.
@WebMethod - jest użyteczne gdy mamy wiecej metod w beanie i chcemy żeby tylko niektóre były "eksportowane" do WSDL'a.

Akt IV

A teraz jak tego użyc?
Najpierw musimy wrzucić nasz projekt na serwer, następnie tworzymy w eclipsie nowy projekt, naciskamy Ctrl + N, z menu wybieramy 'Web Services', 'Web Service Client', w 'Service Definition' wklejamy link do naszego WSDL (link możemy znalezć na stronie, nie musimy sie przekopywać przez tony logów tak jak np. w Geronimo), klikamy 'Finish' i mamy utworzony interfejs do połączenia z naszym WebSerwisem, teraz napiszemy klase w ktorej przetesujemy jego dzialanie.

public class Test {

public static void main(String[] args) {

AutorWSBeanService abs = new AutorWSBeanServiceLocator();

try {
AutorWSInterface asi = abs.getAutorWSBeanPort();
String[] autor = asi.pobierzAutora(1);
System.out.println(autor[0] + " " + autor[1]);
} catch (ServiceException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}

}


Happyend

Uruchamiamy powyższą aplikacje lokalnie. A poniżej mamy wynik działania programu.

Adam Mickiewicz



Tu byl wielki babol rzeczowy;)

8 komentarzy:

  1. Fajnie czyta się taki krótki przepis na usługę sieciową. Na prawdę relaksujące ;-)

    Kilka uwag:

    public Autor() {
    super();
    }

    niepotrzebne, bo będzie dostarczony domyślnie przez kompilator.

    Po co @SOAPBinding(style = Style.DOCUMENT) przy interfejsie i implementacji?

    Nie prościej implementować (realizować) interfejs w ten sposób pozbywając się atrybutu enpointInterface?

    Jeśli persistence.xml zawiera pojedynczą definicję PU to można opuścić atrybut unitName w @PersistenceContext.

    Publiczne metody bezstanowego ziarna EJB są automatycznie metodami usługi sieciowej - niepotrzebna @WebMethod przy pobierzAutora.

    Od dzisiaj "select a from Autor as a where a.id = " + idAutora jest zabronione! tego nie wolno Ci robić! Uważaj na atak typu "SQL injection". Warto skorzystać z nazwanych zapytań (named queries) i parametrów zapytania. Rozpracowanie tematu zostawiam jako pracę domową ;-)

    Dlaczego metoda pobierzAutora zwraca ArrayList, w którym będą dwa ciągi? Nie prościej po prostu zwrócić Autora?!

    "musi wskazywać na intefejs" - nic nie musi - patrz komentarz wyżej.

    "a nie każdy musi mieć implemetacje list" - a skąd to ograniczenie? Jaki język nie posiada listy i potrafi korzystać z usług sieciowych? Tutaj czuję ograniczenie na wyrost. Oczekuję wyjaśnień ;-)

    Jacek

    OdpowiedzUsuń
  2. Wszystkie materiały które znalazłem na ten temat były pobieżne, ew dość zawile opisane.

    1.
    public Autor() {
    super();
    }

    Narzut z eclipsa

    2.
    @SOAPBinding(style = Style.DOCUMENT)

    Racja, niepotrzebne, pozostałość po moich eksperymentach z różnymi style i use

    3.
    "Jeśli persistence.xml zawiera pojedynczą definicję PU to można opuścić atrybut unitName w @PersistenceContext."

    To jest spowodowane moja niewiedzą. "Tak nas uczono". Dzięki za uwagę.

    4.
    "select a from Autor as a where a.id = " + idAutora.
    Zaczerpniete z zajec, u hmm.. imienia nie bede chyba wymienial;) Bylem swiecie przekonany ze JEE, tak jak znienawidzony pehap potrafi escape'owac zapytania i ustrzec sie przed SQL inj...

    5.
    "a nie każdy musi mieć implemetację list"
    Przykład: pomimo tego ze python ma niezgorszą implementacje list, nie potrafi poprawnie wyciągnąć odpowiedzi od javowego webserwisu, a o (o zgrozo!) obiekcie Autor juz nie mówiąc (niezależnie od przestrzeni nazw, bo to chyba od tego po części zależy), zwaliłem to ograniczenia WSDL.

    OdpowiedzUsuń
  3. Po chwili zastanowienia dochodze do wniosku, iż NamedQuery w moim wypadku sa zbedne, poniewaz uzytkownik w żaden sposob nie moze jawnie nic przekazać do zapytania, te wszystkie id albo sa wybierane z predefiniowanych list, albo sa wyklikane na podstawie istniejacego wpisu pobranego z richfacesowego data table. Aczkolwiek obiecuje ze bede omijal czyste zapytania sqlowe szerokim lukiem;)

    OdpowiedzUsuń
  4. Zapominasz jednak, że haker nie będzie korzystał z interfejsu webowego, a użyje własnego klienta, który korzysta z danych spreparowanych - zero wybierania z listy z RichFaces, czy innego cuda. Zapomnij o tym. NamedQuery są konieczne, obowiązkowe i co tam jeszcze sobie można wymyśleć. Poza tym, zapytania mogłyby trafić do adnotacji @NamedQuery, albo orm.xml, a nie być zanurzone gdzieś w kodzie.

    OdpowiedzUsuń
  5. W końcu doszukałem się dlaczego nasze zapytania są złe. Z tego co wyczytałem chodzi nie tyle o named query co o bindowanie.

    Query query = em.createQuery( "select p from Person p where p.name = :name");
    query.setParameter( "name", name );

    Przez interfejs webowy masz na myśli same strony czy wliczamy w to MBeany ?

    OdpowiedzUsuń
  6. Interfejs webowy, ale nie z przeglądarki, tylko z jakiegokolwiek innego narzędzia obsługującego http, w której nie uruchamiasz kontrolnego kodu javascript.

    OdpowiedzUsuń
  7. Zgadzam się że namedQuery to jedynie słuszne rozwiązanie przy wykorzystaniu bezpośredniego interfejsu aplikacji. Jest tylko jeden problem z webServicami pod JBossem - potrzebują JBoss'a ;). Proponuje inne rozwiązanie oparte na springframework i dowolnym kontenerze serwletów oraz cxf jako implementacji standardu

    OdpowiedzUsuń
  8. fajny blog pokazujący konkretne problemy i ich rozwiązania - szkoda że tak szybko przestałeś pisać - może jest szansa żeby tu wrócić przecież blogów o pisaniu programów jest w polskim internecie jak na lekarstwo

    OdpowiedzUsuń