Tag Archives: J2EE

Eskalace v CzechIdM – jBPM timery

Docent Novák si zažádal o přístup do specializované knihovny, ve které by chtěl najít odpověď na otázku, na kterou během výzkumu narazil. Žádost už ale šest dní leží někde na stole a nevypadá to, že se jí v brzké době vůbec někdo bude zabývat. Pan Pokorný, který má knihovnu na starost, totiž odjel na dlouhou služební cestu a vrátí se až za několik týdnů.

Jak situaci řešit? Continue reading

Hibernate: cizí klíče bez cizích klíčů

Při implementaci našeho Identity Manageru CzechIdM jsme narazili na zajímavý problém týkající se technologie Hibernate a struktury tabulek v databázi. Potřebovali jsme zajistit funkcionalitu, kterou běžně poskytují v databázi cizí klíče. Jenže právě cizí klíče jsme použít nemohli. V tomto článku se na náš problém podíváme a vysvětlíme si, jak se nám ho podařilo vyřešit s použitím některých pokročilejších Hibernate anotací.

Continue reading

Časovače, Java a CzechIdM – 1. část

Toto je první část příspěvku zaměřeného na časovače a časové služby v programovacím jazyce Java. V dnešním příspěvku si na úvod uvedeme dva příklady, kde najdou časovače své uplatnění. První je spíše obecného charakteru, druhý je již specifický a popisuje použití časovačů v našem systému CzechIdM. Poté si ukážeme, jak časovat úlohy v Java SE a Java EE 5 a 6 s použitím EJB kontejneru.

Příští příspěvek bude zaměřen více specificky, a to na jBPM časovače a na projekt Quartz. Zároveň si jednotlivé možnosti shrneme a porovnáme. Nezbývá než začít.

Continue reading

JAXB – ještě bližší propojení Javy a XML

Java i XML jsou technologie, které se často používají pro komunikaci mezi aplikacemi na různých operačních systémech. Proto je v Javě řada možností, jak s XML daty pracovat, např. SAX, DOM atd.. Jednou z dalších technologií provazující Javu s XML je i JAXB (Java Architecture for XML Binding), jejíž použití si ukážeme na praktických příkladech. JAXB nabízí metody pro konverzi XML dat na Java objekty a naopak, a umožňuje zápis a čtení XML z mnoha různých zdrojů, například ze souboru, streamu, nebo z URL. V tomto článku si na několika příkladech ukážeme, jak se s JAXB pracuje.

Continue reading

JSF – použití AJAXu s frameworkem Rich Faces


Cílem tohoto článku je ukázat, jak snadno můžeme používat AJAX s webovými frameworky Java Server Faces a Rich Faces.

AJAX (Asynchronous Javascript and XML) je technologie, pomocí které měníme obsah webové stránky bez nutnosti jejího opětovného načtení. Tento způsob obnovy dat na stránce je rychlejší, šetrnější k serverovému výkonu a uživatelsky příjemnější než způsob, při kterém znovu načítáme celou stránku.

V aplikaci kterou nyní vyvíjíme jsme se rozhodli technologii AJAX používat. Prezentační vrstvu programujeme ve frameworku JSF 1.2 (neřešme proč nepoužíváme JSF 2.0, kde je podpora AJAXu lepší). Společně se standardními komponentami, které JSF mají, používáme komponenty Rich Faces.

Rich Faces je knihovna, která obsahuje velké množstní hotových grafických komponent a dále pak komponenty, které “za nás” implementují Ajaxové volání mezi klientem a serverem. Tato funkcionalita výrazně urychluje vývoj a je jednoduchá na naučení. Jednoduchost použití budu demostrovat na příkladu, který bude řešit následující úkol:

Vytvořte webovou stránku, na které zobrazte 2 tabulky se jmény sportů. V tabulce A bude seznam všech sportů, které bude možné přidat do tabulky B. Přidání se provede kliknutím na odkaz “Pridat”, tzn. že po jeho stisknutí se jméno přidá do tabulky B a odebere z tabulky A. V tabulce B bude tedy seznam vybraných sportů a bude možné sport odebrat. Odebrání se provede kliknutím na odkaz “Odebrat”, tzn. že po jeho stisknutí se název přidá do tabulky A a odebre z tabulky B. Navíc se po každé akci zobrazí v info panelu pod tabulkami informace o naposledy provedené akci.

Nejprve si vytvoříme tzv. JSF Managed Beanu (pro jednoduchost se data načítají přímo v konstruktoru a nepoužívají se konstanty za texty):

package eu.bcvsolutions.ui.bean;

import java.util.ArrayList;
import java.util.List;

public class TestBean {

	private List selectedSports;
	private List allSports;
	private String sport;
	private String infoMessage;
	
	public TestBean() {
		selectedSports = new ArrayList();
		allSports = new ArrayList();
		
		allSports.add("Fotbal");
		allSports.add("Basketbal");
		allSports.add("Hokej");
		allSports.add("Formule");
		allSports.add("Tenis");
	}
	
	public String addToSelectedSports() {
		selectedSports.add(sport);
		allSports.remove(sport);
		setupAddActionToInfoMessage();
		return null;
	}
	
	public String removeFromSelectedSports() {
		selectedSports.remove(sport);
		allSports.add(sport);
		setupRemoveActionToInfoMessage();
		return null;
	}

	public List getSelectedSports() {
		return selectedSports;
	}

	private void setupAddActionToInfoMessage() {
		String message = "Uspesne jste pridali uzivatele %s";
		infoMessage = String.format(message, sport);
	}
	
	private void setupRemoveActionToInfoMessage() {
		String message = "Uspesne jste odebrali uzivatele %s";
		infoMessage = String.format(message, sport);
	}
	
	public void setSelectedSports(List selectedSports) {
		this.selectedSports= selectedSports;
	}

	public List getSllSports() {
		return allSports;
	}

	public void setSllSports(List allSports) {
		this.allSports= allSports;
	}

	public String getSport() {
		return sport;
	}

	public void setSport(String sport) {
		this.sport= sport;
	}

	public String getInfoMessage() {
		return infoMessage;
	}

	public void setInfoMessage(String infoMessage) {
		this.infoMessage = infoMessage;
	}
}

V této ukázce je nastaven scope na hodnotu “page”. Nastavení scope provádím v souboru components.xml (používám navíc interační framework Seam), pokud používáte “čisté” JSF, nastavíte scope ve faces-config.xml.

Kód beany je vcelku jasný – beana udržuje informace o všech názvech, o vybraných názvech, o vybraném sportu, je zde umístěn text informačního panelu a obsluhují se události po stisknutí tlačítek.

Dále vytvoříme následující xhtml stránku:

<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
	xmlns:s="http://jboss.com/products/seam/taglib"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:h="http://java.sun.com/jsf/html" 
	xmlns:a4j="http://richfaces.org/a4j" 
	xmlns:bcv="http://www.bcvsolutions.eu/jsf" 
	xmlns:rich="http://richfaces.org/rich" 
	xmlns:c="http://java.sun.com/jstl/core" >

<html>
<head>
<title>BCV solutions s.r.o. - Ukazka pouziti technologii JSF, AJAX, Rich Faces</title>
</head>

<body>
	<a4j:form>		
	<table>
	<tbody>
	<tr>
	<td valign="top">
	  Vybrane sporty
	  <br/>
	  <rich:dataTable value="#{testBean.selectedSports}" var="sport" id="selectedTable">
									
	    <rich:column>
	      <f:facet name="header">
	         Nazev
	      </f:facet>
	      <h:outputText value="#{sport}" />
	   </rich:column>
	   <rich:column>
	      <f:facet name="header">
	        Akce
	      </f:facet>
	      <a4j:commandLink value="Odebrat" action="#{testBean.removeFromSelectedSports}"
                    reRender="selectedTable, allTable, infoMessage">
                    <f:setPropertyActionListener value="#{sport}" target="#{testBean.sport}"/>
	      </a4j:commandLink>
	    </rich:column>	
	  </rich:dataTable>
	</td>
	<td valign="top">
	  Seznam sportu
	  <br/>
	  <rich:dataTable value="#{testBean.allSports}" var="sport" id="allTable">
	    <rich:column>
	      <f:facet name="header">
	        Nazev
	      </f:facet>
	      <h:outputText value="#{sport}" />
	    </rich:column>
	    <rich:column>
	      <f:facet name="header">
	        Akce
	      </f:facet>
	      <a4j:commandLink value="Pridat" action="#{testBean.addToSelectedSports}"
                            reRender="selectedTable, allTable, infoMessage">
                  <f:setPropertyActionListener value="#{sport}" target="#{testBean.sport}"/>
	      </a4j:commandLink>
	    </rich:column>	
	  </rich:dataTable>
	</td>
	</tr>
	</tbody>
	</table>
	<h:outputText id="infoMessage" 
		value="#{testBean.infoMessage}"
		style="background-color: #ffff99"/>
	</a4j:form>
</body>
</html>

</ui:composition>

Na této stránce není vůbec nic složitého. Jediné, co stačilo udělat proto, aby se nemusela znovu přenášet všechna data a posílal se jen obsah tabulek a obsah info panelu (nyní na stránce žádný další obsah není, ale v reálné aplikaci by zajisté byl) bylo použití komponenty a4j:commandLink. Rozdílem oproti standardnímu h:commandLink je v parametru reRender. Tomu jsme předali ID komponent, jejichž obsah má být obnoven. Dále jsme místo standardní formulářové komponenty h:form použili komponentu frameworku Rich Faces a4j:form (v tomto případě by to však ani nebylo nutné).

Nyní si stručně popišme použité komponenty:

  • a4j:form

    • obdoba h:form, která řeší 2 základní nedostatky:

      • pokud je nastaven parametr ajaxSubmit na true, umožňuje komvertovat všechny „neajaxové“ volání komponent na ajaxové
      • umožňuje obnovení komponenty h:commandLink bez nutnosti obnovení celého formuláře
  • a4j:commandLink

    • obdoba h:commandLink, rozdíl je v tom, že tato komponenta generuje Ajax request a umožňuje updatovat jenom část stránky

Výsledek naší ukázky vypadá takto:

Dá se říct, že naš kód je obdobou komponenty rich:pickList. My jsme však tuto komponentu nevyužili, protože jsme potřebovali komplexnější funkcionalitu. Podobným způsobem, kterým jsme vytvářeli tyto dvě tabulky je např. možné po stisknutí zobrazit editační formulář, modální panel do kterého pomocí ajaxu dotáhneme kompletní data o sportu apod.


Vytváříte ve vaši firmě webové aplikace na platformě Java? Potřebujete navrhnout architekturu jádra aplikace či webového rozhraní? Neváhejte se obrátit na tým našich vývojářů a konzultantů.

Tvorba komponent v JSF

Cílem tohoto článku je ukázat, jakým způsobem je možné vytvořit vlastní grafickou komponentu při práci s webovým frameworkem JSF a Facelets.

Používáme-li “čisté” JSF, je vytvoření vlastní komponenty poměrně složitou záležitostí. Framework Facelets nám umožňuje vytvořit snadno a rychle komponenty vlastní. Pravda, takto vytvářené komponenty nemohou být vždy tak komplexní, jako komponenty ostatní (tj. ty, které jsou naprogramovány pomocí Java tříd), na vyřešení spousty požadavků však bohatě stačí.

V tomto příspěvku si ukážeme, jak vyřešit následující úkol:

Vytvořte znovupoužitelnou komponentu, která po stisknutí tlačítka zobrazí pop up okno se seznamem uživatelů, přičem po vybrání uživatele se pop up okno zavře, ID vybraného uživatele se uloží do JSF managed beany a ve formuláři v hlavním okně se do input políčka načte příjmení vybraného uživatele.

Možná vás napadne, proč pro tento úkol vytvářet nějakou komponentu. Pokud bychom tuto funkčnost použili v aplikaci jen jednou, nemělo by to samozřejmě žádný smysl. V aplikaci, kterou nyní vyvíjíme, je však výše zmíněná funkčnost potřeba hned na několika místech. Díky vlastní komponentě nebudeme v aplikaci psát několikrát stejný kód a navíc je možné, že vytvořenou komponentu použijeme i jinde.

Jak tedy začít? Nejprve je nutné vytvořit soubor, ve kterém budeme definovat naše nové komponenty – tzn. jejich jméno a cestu k nim. Tento soubor umístěme např. sem:

  • aplikace/WebContent/WEB-INF/facelets/tags/components.taglib.xml

    Zde je ukázka tohoto souboru:

    <?xml version="1.0"?>
    <!DOCTYPE facelet-taglib PUBLIC
      "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
      "facelet-taglib_1_0.dtd">
    <facelet-taglib>
          <namespace>http://www.bcvsolutions.eu/jsf</namespace>
          
          <tag>
          	<!-- 
          	Atributy:
          	
          	labelName: Nadpis modálního panelu. (muze byt null)
          	panelId: Id panelu. (muze byt null)
          	reRender: Id políčka (nebo políček), které se po výběru identity obnovi. (muze byt null)
          	selectedIdentity: Atribut, do ktereho se bude setovat vybraný uživatel. (musi se nastavit)
          	
          	 -->
    		<tag-name>findUserPanel</tag-name>
    		<source>find-user-panel-component.xhtml</source>
    	  </tag>
          
    </facelet-taglib>
    

    Náše nová komponenta se tedy bude jmenovat findUserPanel a bude definována v souboru find-user-panel-component.xhtml, který bude umístěn ve stejném adresáři jako components.taglib.xml.

    Nyní je nutné v konfiguračním souboru web.xml naši definovanou knihovnu facelets tagů (komponent):

    <context-param>
            <param-name>facelets.LIBRARIES</param-name>
            <param-value>/WEB-INF/facelets/tags/components.taglib.xml</param-value>
     </context-param>
    

    Definice naší komponenty (soubor find-user-panel-component.xhtml) bude vypadat takto:

    <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <ui:composition xmlns="http://www.w3.org/1999/xhtml"
    	xmlns:s="http://jboss.com/products/seam/taglib"
    	xmlns:ui="http://java.sun.com/jsf/facelets"
    	xmlns:f="http://java.sun.com/jsf/core"
    	xmlns:h="http://java.sun.com/jsf/html"
    	xmlns:rich="http://richfaces.org/rich"
    	xmlns:a4j="http://richfaces.org/a4j"
    	xmlns:c="http://java.sun.com/jstl/core">
    
    	<c:if test="${empty labelName}">
        	     <c:set var="labelName" value="#{messages.admin_users_find_panel_label}" />
    	</c:if>
    	
    	<c:if test="${empty panelId}">
        	     <c:set var="panelId" value="identityPanel" />
    	</c:if>
    	
    	<c:if test="${empty setupNullLabel}">
        	     <c:set var="setupNullLabel" value="#{messages.admin_workflow_btn_runAs_defalut}" />
    	</c:if>
    
    	<rich:modalPanel id="#{panelId}" width="650" height="400">
    		<f:facet name="header">
    			<h:panelGroup>
    				<h:outputText value="#{labelName}"></h:outputText>
    			</h:panelGroup>
    		</f:facet>
    		<f:facet name="controls">
    			<h:panelGroup>
    				<h:graphicImage url="/img/close.png" alt="close"
    					styleClass="hidelink" id="hidelink#{panelId}" />
    				<rich:componentControl for="#{panelId}"
    					attachTo="hidelink#{panelId}" operation="hide" event="onclick" />
    			</h:panelGroup>
    		</f:facet>
    
    		<a4j:commandButton value="#{setupNullLabel}" 
    			reRender="#{reRender}"
    			oncomplete="#{rich:component(panelId)}.hide()"
    			immediate="true">
    			<f:setPropertyActionListener value=""
    				target="#{selectedIdentity}" />
    		</a4j:commandButton>
    		
    		<br/>
    
    		<rich:dataTable value="#{findUserComponentBean.users}" var="user" rows="20"
    			reRender="ds" id="simpletable"
    			onRowMouseOver="this.style.backgroundColor='#F1F1F1'"
    			onRowMouseOut="this.style.backgroundColor='#{a4jSkin.tableBackgroundColor}'">
    			<f:facet name="header">
    				<h:panelGroup>
    					<h:outputText value="#{messages.admin_users_find_table_title}"></h:outputText>
    				</h:panelGroup>
    			</f:facet>
    			<rich:column sortBy="#{user}">
    				<f:facet name="header">
    					<h:outputText value="#{messages.admin_user_id}" />
    				</f:facet>
    				<h:outputText value="#{user}" />
    			</rich:column>
    
    			<rich:column>
    				<f:facet name="header">
    					<h:outputText value="#{messages.cmn_action}" />
    				</f:facet>
    
    				<a4j:region>
    					<a4j:commandButton value="#{messages.cmn_btn_select}" reRender="#{reRender}"
    						oncomplete="#{rich:component(panelId)}.hide()"
    						immediate="true">
    						<f:setPropertyActionListener value="#{user}"
    							target="#{selectedIdentity}" />
    					</a4j:commandButton>
    				</a4j:region>
    
    			</rich:column>
    			<f:facet name="footer">
    				<rich:datascroller id="ds" renderIfSinglePage="false" maxPages="1"></rich:datascroller>
    			</f:facet>
    		</rich:dataTable>
    
    	</rich:modalPanel>
    
    </ui:composition>
    

    A nyní si kód popišme:

  • Nejprve testujeme, jestli byla na vstupu zadána proměnná labelName – pokud ne, vyplníme ji defaultní hodnotou. Text obsažený v proměnné bude zobrazen jako nadpis okna.
  • Obdobným způsobem testujeme proměnnou panelId. Každá komponenta na JSF stránce musí mít jedinečné ID, mohlo by se stát, že bychom naši komponentu chtěli použít vícekrát na stejné stránce a podmínka na stejné ID by byla porušena – proto umožňujeme ID nastavit.
  • Opět stejný způsob – test, definice proměnné setupNullLabel – hodnota proměnné bude textem tlačítka, které zajistí vynulování hodnoty v managed beaně a v input políčku.
  • Definujeme komponentu rich:modal panel, která zajistí zobrazení pop up okna.
  • Tlačítko a4j:commandButton nastaví prázdný řetězec do proměnné selectedIdentity, zavře pop up okno a provede refresh input boxu (respektive komponenty s ID = proměnné reRender)
  • Dále definujeme tabulku, která zobrazí seznam uživatelů. U každého uživatele bude tlačítko, po jehož stisku se nastaví informace o vybraném uživateli na managed beanu, provede se refresh input políčka a zavře se “vyskakovací” okno.

Následující kód je JSF managed beana, kterou komponenta používá:

public class FindUserComponentBean {
	
	/** Seznam uživatelských jmen nalezených identit. */
	private List users;
	/** Vybraný uživatel z tabulky */
	private String selectedUser;
	public FindUserComponentBean(){
	}	
	public String search(){
		users = Data.find(View.Type.USER, new Criteria());
		return null;
	}
	public String selectUser(){
		return null;
	}
	public List getUsers() {
		if(users == null){
		      search();
		}
		return users;
	}

	public void setUsers(List users) {
		this.users = users;
	}

	public String getSelectedUser() {
		return selectedUser;
	}

	public void setSelectedUser(String selectedUser) {
		this.selectedUser = selectedUser;
	}
}

Nyní se konečně dostáváme v ukázce použití této komponenty:

<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
	xmlns:s="http://jboss.com/products/seam/taglib"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:h="http://java.sun.com/jsf/html" 
	xmlns:a4j="http://richfaces.org/a4j" 
	xmlns:bcv="http://www.bcvsolutions.eu/jsf" 
	xmlns:rich="http://richfaces.org/rich" 
	xmlns:c="http://java.sun.com/jstl/core" 
	template="usertemplate.xhtml">

<h:form>
    <table>
	<tr>
	   <th><h:outputText value="#{messages.name}"/></th>
	   <td><h:inputText value=“#{myBean.name}“</td>
	</tr>
	<tr>
	   <th><h:outputText value="#{messages.boss}"/></th>
	   <td>
               <h:inputText value="#{myBean.boss}" id="boss" disabled="true"/>
                   <h:outputLink value="#" id="linkBoss">
                      <h:outputText value="#{messages.cmn_btn_select}"/>
                      <rich:componentControl for="selectIdentity" attachTo="linkBoss" operation="show" event="onclick"/>
                  </h:outputLink>
          </td>
        </tr>
     </table>

	<bcv:findUserPanel panelId="selectBoss" reRender="boss" 
		selectedIdentity="#{myBean.boss}"
		setupNullLabel="#{messages.no_boss}"/>
	. . . 
	</h:form>
</ui:composition>

Všiměme si definice namespacu bcv:, pomocí něho se odkazujeme na naše komponenty. Celou komponentu by bylo možné (vhodné) upravit např. tak, aby i vytvořila input políčko a definici modal panelu vložila za něj. Dále by bylo možné doplnit do pop up okna tlačítko hledat a input boxy pro zadávání vyhledávacích kritérií.

A jaké jsou vaše zkušenosti s tvorbou vlastních komponent ve webových aplikacích?

Automatické testování Java aplikace pomocí nástrojů TestNG a Apache ANT (Unit testing)

Tento článek popisuje způsob automatického testování JAVA aplikací pomocí nástrojů TestNG a Apache ANT. Testovaná aplikace je napsána na platformě J2EE pomocí frameworku JBoss Seam.

Před časem jsem dostal za úkol vyřešit automatické spouštění unit testů pro aplikaci, kterou vyvíjíme. Jako vývojové prostředí používáme Eclipse. Samotné unit testy píšeme pomocí nástroje TestNG. Cílem tedy bylo vytvořit nástroj, který si stáhne aktuální verzi aplikace z SVN repository. Aplikaci zkompije a spustí testy. Tedy se jedná o tzv. unit testy.

Úkol jsem si nejprve rozdělil na několik dílčích částí:

  • stažení dat z SVN serveru
  • build aplikace
  • spuštění unit testů

Stažení dat z SVN, jak se později ukázalo, je velice snadné. Použil jsem Apache ANT a knihovny pro práci s SVN:

<path id="svnant.class.path">
	<pathelement location="lib\svnant.jar" />
	<pathelement location="lib\svnClientAdapter.jar" />
	<pathelement location="lib\svnjavahl.jar" />
	<pathelement location="lib\svnkit.jar" />
</path>

<typedef resource="org/tigris/subversion/svnant/svnantlib.xml" classpathref="svnant.class.path" />

<target name="svn">
	<svn username="username" password="password"> 
		<checkout url="url" revision="HEAD" destPath="path" />
		<!--<update revision="HEAD" dir="${projectPath}" />-->
	</svn>
</target>

Checkout se použije při prvním stažení, poté se neprovádí checkout, ale update.

Problém nastal u buildu aplikace. Jak již jsem zmiňoval, jako vývojové prostředí používáme Eclipse a plugin pro práci s frameworkem JBoss Seam. Tento plugin nám zajišťuje build aplikace a deploy na aplikační server. Plugin pro práci s TestNG nám zajišťuje spouštění testů – tzn. deploy aplikace na tzv. embedded JBoss a provedení testů.

Zdálo se zbytečné psát pomocí buildovacího nástroje ANT či Maven script pro build aplikace. Proto jsem se rozhodl vyhledat nástroj (plugin), který by byl schopný vzít data z Eclipse workspace a provést build. Našel jsem nástroj ant4eclipse, o kterém jsem se dočetl, že podobnou věc umí.

Po mnoha neúspěšných pokusech jsem zaslal dotaz do fóra, ve kterém jsem se ptal, jakým způsobem se provádí build Java aplikace (Seam + J2EE + JSF). Překvapivě rychle jsem dostal odpověď, že nástroj takovouto aplikaci buildit neumí. (Zajímá-li vás však build „obyčejné“ Java aplikace, určitě vám doporučuji se na nástroj podívat.)

Protože jsem žádnou další alternativu neobjevil, nezbylo mi nic jiného, než napsat vlastní ANT build script. Přesto, že jsem s ANT-em nikdy nepracoval, nezdál se být úkol až tak těžký. Podíval jsem se, jak vypadá build, který provádí Eclipse – tedy ten, který se nachází na aplikačním serveru (v našem případě to byl JBoss AS). Tohoto jsem se držel – jen tak mimochodem, již po několika desítkách minut práce s ANT-em zjistíte, proč se nástroj jmenuje ANT – je to opravdu mravenčí práce. :-)

Když už se konečně zdálo, že mám hotovo, hodně jsem se zmýlil! Sice jsem vytvořil strukturu aplikace, kterou bylo možné nahrát na aplikační server, problém však byl se spuštěním testů.

Nástroj TestNG spouští totiž testy ne přímo na aplikačním serveru, ale na tzv. embedded JBoss. Tady jsem opravdu narazil, protože jsem nebyl schopen nakonfigurovat buildovací script tak, aby úspěšně spustil embedded JBoss a provedl testy.

Prošel jsem si ukázkové aplikace k frameworku Seam a zjistil jsem, že je zde možné testy spouštět. Protože spuštění mi zafungovalo a testy se úspěšně provedly, řekl jsem si, že projdu buildovací scripty ukázkové aplikace a spustím testy obdobným způsobem. (Tady jsem si potvrdil svůj úsudek o tom, že psaní a editace build scriptů je opravdu mravenčí práce) Skutečně jsem spouštění testů nalezl, zkopírovaní a použití v naší aplikaci však bylo velice komplikované. Sada buildovacích scriptů examplů se všemožně includuje a nalézt skutečné hodnoty zástupných proměnných bylo poměrně dost náročné.

Jelikož i zde jsem narazil, napadla mě jiná možnost – použít buildovací script pro ukázkovou aplikaci Seamu na naši aplikaci. Jelikož tato ukázková aplikace má jinou strukturu, bylo nutné upravit výstup z mého ANT-ovského buildu do podoby, která bude totožná s ukázkovým příkladem Seamu.

Toto je podoba upraveného projektu do podoby, ve které jsou všechny Seam examply:

  • jboss-seam
    • bootstrap
    • build
    • classes
    • examples
      • naše aplikace
        • resources
        • src
        • view
        • build.xml // zkopírovaný ze seam examplu. Stačí provést editaci názvu aplikace a resourcu – tzn. souboru, kde se definuje URL databáze.
    • lib
    • test-output
    • ui
    • build.xml // testo soubor je přesně ten, který je v examplech, build.xml z naší aplikace pomocí něho spouští TestNG testy

Pro úspěšné spuštění testů stačí spustit build.xml (target = test) z naší aplikace:

Jen pro doplnění – vytvoření struktury, která je shodná s tou, kterou generuje Eclipse nebylo zbytečné – je možné ji automaticky deployovat do testovacího prostředí. Samozřejmě automatické spouštění scriptu je třeba nastavit na testovacím serveru.

Na závěr tedy shrnutí postupu:

  • stažení aktuální verze aplikace z SVN repository
  • odstranění předchozího buildu
  • build EJB vrstvy
  • build webové vrstvy
  • build unit testů
  • zkopírování buildu do souboru /seam/examples/nase_aplikace
  • spuštění příkazu ant -f seam/examples/nase_alikace/build.xml test
  • Přestože řešení buildu není úplně „čisté“, je použitelné. Máme tak nástroj, který automaticky testuje aktuální verzi naší aplikace. Jako odměnu pro ty, kteří dočetli až sem, si dovolím přidat trochu teorie a pojmů o testování SW, kterou jsem pochytil ve školních lavicích :-)

    Testování jednotek
  • Zaměřujeme se na malé kusy zdrojového kódu – na jednotlivé funkce.
  • Testujeme např. okrajové podmínky, cesty pro zpracování chyb,…
  • Integrační testování
  • Integrace zdola nahoru
    • spojíme nejnižší moduly
    • napíšeme řídící modul, který bude koordinovat práci mezi spojenými moduly
    • otestujeme skupinu
    • odebereme řídící modul a pospojujeme se skupinami ve vyšší struktuře
  • Integrace shora dolů
    • Vezmeme hlavní modul a maketami nahradíme nižší moduly
    • Postupně měníme makety za reálné moduly s tím, že po každém přidání modulu systém otestujeme.
    • Cílem je ověřit, že přidáním modulu nedošlo k zanesení chyby do aplikace.
    • Nevýhodou je nutnost tvorby maket modulů.
  • Validační testování
  • Slouží k ověření, že aplikace splňuje požadavky zákazníka.
  • Provádí se metodami black-box testování.
  • Do této kategorie spadají např. akceptační testy.
  • Systémové testování
  • Skupina různých testů, která prověřuje celou systémovou architekturu (HW, SW, administrátory).
  • Bezpečnostní testování (odolnost proti útokům hackerů).
  • Testování obnovy (ověření, že poruchy byly odstraněny ve smluveném termínu).
  • Zátěžové testování (sledování chování programu při velikém zatížení – tzn. simulace velkého počtu připojených uživatelů).
  • Black-box testování
  • Jedná se o testování správného provádění navržených funkcí aplikace. Ne vždy u tohoto způsobu známe všechny potřebné informace o implementaci.
  • Tento způsob testování nemusí nutně postihnout celý kód aplikace.
  • White-box testování
  • Při tomto způsobu testování známe informace o vnitřní struktuře aplikace, kterou testujeme.
  • Testujeme
    • všechny samostatné cesty uvnitř modulu
    • všechny podmínky se projdou větví s hodnotami ano i ne
    • všechny cykly
    • všechny vnitřní datové struktury
  • Na úplný závěr si dovolím jednu větu, která charakterizuje testování (rád bych citoval, ale autora věty si nepamatuji):

    Úspěšný test je takový, který objevil doposud neobjevené chyby.

Automatické kopírování entitních objektů do DTO objektů

Před časem jsme vytvořili návrh aplikace, která mezi prezentační a aplikační vrstvou, namísto entitních objektů, přenáší tzv. DTO (Data Transfer Object) objekty. Základní výhodou tohoto přístupu předávání objektů je úplná nezávislost aplikační vrstvy na vrstvě prezentační. Při vývoji bylo nutné si co nejvíce usnadnit práci s kopírováním dat z entitního objektu do DTO objektu.

DTO objekt může nést atributy z jedné nebo i z několika entit. Cílem bylo vytvoření utilitky, která na vstup dostane entitní a DTO objekt a překopíruje data z entitního objektu do DTO objektu. Toto nám usnadní “ruční” kopírování a neustálé psaní dto.setValue(entita.getValue). Alternativou k této funkčnosti je samozřejmě funkčnost opačná, tedy přenost dat z DTO objektu do entitního objektu.

Dalšími požadavky na funkčnost jsou:
1) možnost nastavit atribut DTO třídy tak, že se ho utilita nebude snažit nastavovat hodnotou z entitní třídy
2) možnost určit u atributu DTO třídy entitní třídu, ze které se bude nastavovat jeho hodnota

Právě podobně nastíněný problém je snadné vyřešit díky tzv. reflexi v kombinaci s vytvořením vlastních anotací.

Pro splnění požadavku (1) jsme si vytvořili následující anotaci:

package eu.bcv;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface BCVNonCopy {

}

Anotace @Retention nám říká, že naše anotace bude dostupná za běhu.

Pro splnění požadavku (2) jsme si vytvořili další anotaci:

package eu.bcv;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface BCVCopy {

	public Class entityClass() default Object.class;
	
}

Entitní objekt, který budeme v tomto vzorovém příkladu používat vypadá takto:

package eu.bcv.entity;

public class IdentityEntity {
	private String id;
	private Integer age;
	private boolean admin;
	private double data;
	private Boolean admin2;

	public IdentityEntity() {
		
	}

	// dale gettery a settery
}


DTO objekt, do kterého budeme kopírovat data vypadá takto:

package eu.bcv.dto;

import eu.bcv.BCVCopy;
import eu.bcv.BCVNonCopy;
import eu.bcv.entity.IdentityEntity;
import eu.bcv.entity.RoleEntity;

@BCVCopy(entityClass=IdentityEntity.class)
public class IdentityDTO {

	private String id;
	private Integer age;
	
	@BCVCopy(entityClass=RoleEntity.class)
	private String roleName;
	
	private boolean admin;
	
	private Boolean admin2;
	
	@BCVNonCopy
	private IdentityDTO parent = null;

	public IdentityDTO(String id, Integer age, String roleName, boolean admin,
			Boolean admin2, IdentityDTO parent) {
		super();
		this.id = id;
		this.age = age;
		this.roleName = roleName;
		this.admin = admin;
		this.admin2 = admin2;
		this.parent = parent;
	}

	public IdentityDTO(){
	}
	
	// dale gettery a settery
	
}

Další (a poslední) entitou, na které si budeme ukazovat řešení je třída RoleEntity:

package eu.bcv.entity;

public class RoleEntity {

	private String roleName;

	public RoleEntity(String roleName) {
		super();
		this.roleName = roleName;
	}
}

Nyní máme následující cíl: Z databáze si načteme entitní objekt IdentityEntity a RoleEntity. Chceme na prezentační vrstvu poslat DTO objekt, který bude obsahovat atributy z těchto dvou entit.

Podíváme-li se na třídu IdentityDTO tak zjistíme, že je anotovaná @BCVCopy(entityClass=IdentityEntity.class). To nám říká, že všechny atributy (není-li u jednotlivých atributů nastaveno jinak) budou nastavovány těmi atributy, které jsou ve třídě IdentityEntity a mají stejný název. Vyjímkou je atribut roleName, který bude nastavován hodnotou ze třídy RoleEntity. Atribut parent nebude automaticky nastavován vůbec.

Následující třída ReflectionUtils vyřeší zadaný úkol:

package eu.bcv;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class ReflectionUtils {
	
	/**
	 * Fill data from data transfer object into entity
	 * @param entity
	 * @param dto
	 * @return
	 * @throws Exception
	 */
	public static Object fillDTO(Object entity, Object dto) throws Exception {
		// Find class annotation
		BCVCopy bcvCopy = dto.getClass().getAnnotation(BCVCopy.class);
		
		for (Field field : dto.getClass().getDeclaredFields()) {
			if (field.getAnnotation(BCVNonCopy.class) != null) {
				//Field will not be copy
				continue;
			}

			BCVCopy attBcvCopy = field.getAnnotation(BCVCopy.class);
			if (field.getAnnotation(BCVCopy.class) == null) {
				if (entity.getClass() == bcvCopy.entityClass()) {
					//Default class is same as entity class
					dto = copyField(dto, entity, field);
				}
			} else {
				//Field has your own annotation
				if (entity.getClass() == attBcvCopy.entityClass()) {
					//Field´s annotation contain same class as entity class
					dto = copyField(dto, entity, field);
				}
			}
		}

		return dto;
	}

	public static Object fillEntity(Object entity, Object dto) throws Exception {
		// Find class annotation
		BCVCopy bcvCopy = dto.getClass().getAnnotation(BCVCopy.class);
		
		for (Field field : dto.getClass().getDeclaredFields()) {
			if (field.getAnnotation(BCVNonCopy.class) != null) {
				//Field will not be copy
				continue;
			}

			BCVCopy attBcvCopy = field.getAnnotation(BCVCopy.class);
			if (field.getAnnotation(BCVCopy.class) == null) {
				if (entity.getClass() == bcvCopy.entityClass()) {
					//Default class is same as entity class
					entity = copyField(entity, dto, field);
				}
			} else {
				//Field has your own annotation
				if (entity.getClass() == attBcvCopy.entityClass()) {
					//Field´s annotation contain same class as entity class
					entity = copyField(entity, dto, field);
				}
			}
		}
		return entity;
	}

	/**
	 * Copy field from source object into target object
	 * @param target
	 * @param source
	 * @param field
	 * @return
	 * @throws Exception
	 */
	private static Object copyField(Object target, Object source, Field field)
			throws Exception {
		String[] gettersParam = { "get", "is" };
		List sourceGetters = ReflectionUtils.findMethods(source
				.getClass().getMethods(), gettersParam);

		Method getter = null;
		Method setter = null;

		String getterName = constructGetterName(field);
		//Find getter in sourceGetters
		for (Method get : sourceGetters) {
			if (get.getName().equals(getterName)) {
				getter = get;
				break;
			}
		}

		String[] settersParam = { "set" };
		List targetSetters = ReflectionUtils.findMethods(target
				.getClass().getMethods(), settersParam);

		String setterName = constructSetterName(field);
		//Find setter in sourceSetters
		for (Method set : targetSetters) {
			if (set.getName().equals(setterName)) {
				// System.out.println("Nalezeno " + setterName);
				setter = set;
				break;
			}
		}

		if (getter == null || setter == null) {
			throw new Exception("No setter or getter found");
		} else {
			Object param = getter.invoke(source);
			setter.invoke(target, param);
		}

		return target;
	}

	/**
	 * Create getter name for input Field
	 * @param field
	 * @return
	 */
	private static String constructGetterName(Field field) {
		String name = field.getName();
		String prefix = "get";
		if (field.getGenericType().toString().equals("boolean")) {
			prefix = "is";
		}
		return prefix + name.substring(0, 1).toUpperCase()
				+ name.substring(1, name.length());
	}

	/**
	 * Create setter name for input Field
	 * @param field
	 * @return
	 */
	private static String constructSetterName(Field field) {
		String name = field.getName();
		return "set" + name.substring(0, 1).toUpperCase()
				+ name.substring(1, name.length());
	}
	
	/**
	 * Find methods in array which prefix equals input prefixs
	 * @param methods
	 * @param prefix
	 *            Example: "get" or "set"
	 * @return
	 */
	private static List findMethods(Method[] methods, String[] prefixs) {
		List result = new ArrayList();

		for (Method method : methods) {
			String name = method.getName();
			for (String prefix : prefixs) {
				if (name.startsWith(prefix)) {
					result.add(method);
					break;
				}
			}
		}

		return result;
	}

}

Nyní použijeme ReflectionUtils např. následujícím způsobem:

                IdentityEntity iEntita = new IdentityEntity();
		iEntita.setId("novaIdentita");
		iEntita.setAdmin(true);
		iEntita.setAdmin2(true);
		iEntita.setAge(23);
		iEntita.setData(3.3);
		
		RoleEntity rRole = new RoleEntity("BCV_blog");
		IdentityDTO iDTO = new IdentityDTO();
		
		iDTO = (IdentityDTO) ReflectionUtils.fillDTO(iEntita, iDTO);
		iDTO = (IdentityDTO) ReflectionUtils.fillDTO(rRole, iDTO);
		
		System.out.println("ID: " + iDTO.getId());
		System.out.println("AGE: " + iDTO.getAge());
		System.out.println("ADMIN: " + iDTO.isAdmin());
		System.out.println("ADMIN2: " + iDTO.getAdmin2());
		System.out.println("Role name: " + iDTO.getRoleName());

Tzv. vytvoříme entity IdentityEntity a RoleEntity (představme si, že jsme objekty získali z datové vrstvy). Vytvoříme prázdný DTO objekt. DTO předáme společně s entitními objekty třídě ReflectionUtils. Tato třída nám nasetuje do DTO objektu data. Pro kontrolu výsledku si necháme hodnoty DTO vypsat (v tomto případě do konzole).

A co se vlastně děje v ReflectionUtils? Z naší ukázky voláme metodu fillDTO. V té nejprve načteme anotaci BCVCopy, kterou je anotován DTO objekt. Poté procházíme všechny atributy DTO objektu a kopírujeme ty, pro které je splněna jedna z podmínek:

  • Atribut nemá anotaci BCVNonCopy a entitní třída na vstupu je shodná s hodnotou, kterou jsme získali z anotace BCVCopy DTO objektu.
  • Atribut nemá anotaci BCVNonCopy, atribut je anotován anotací BCVCopy, jejíž hodnota je shodná se zadanou entitní třídou.

Kopírování zajišťuje metoda copyField. Ta na vstupu dostane cílový a zdrojový objekt a dále atribut, který se má zkopírovat. Pomocí třídy findMethods nalezneme v entitní třídě gettery (procházíme všechny metody a vybíráme ty, které mají prefix “get” nebo “is”). Poté z názvu atributu, který se bude kopírovat vytvoříme název getteru a vyhledáme getter v seznamu gettrů. Obdobně postupujeme pro nalezení setteru.

Pokud jsme nalezli ve zdrojovém objektu getter a v cílovém objektu setter, získáme hodnotu ze zdroje pomocí konstrukce getter.invoke(source). Nastavení provedeme pomocí konstrukce setter.invoke(target, param).

V závěru bych rád podotknul, že publikovaná verze není finální a kód by se dal napsat efektivněji. Berme tuto verzi spíše jako verzi výukovou.

Celá síle reflexe je zde tedy demostrována na jednom případu – zavolání metody. Tato zdánlivá banalita nám však vývoj velice usnadnila a zpříjemnila, neboť nebylo nutné všechny atributy kopírovat ručně.

Instalace aplikačního serveru JBoss

Tento příspěvek stručně popisuje, jak nainstalovat aplikační server JBoss na server na platformě GNU Linux CentOS 5. Instalované prostředí bude sloužit pro vývoj webových aplikací postavených na jazyku Java.

Instalace JDK

Dřív než se pustíme do instalace samotného aplikačního serveru, musíme mít v systému funkční javu. Je proto potřeba naistalovat Java SE Development Kit (JDK). Já jsem použil verzi Sun JDK, kterou lze získat z nasledující adresy http://java.sun.com/javase/downloads/widget/jdk6.jsp pro JDK 6.0 (vybrat jdk-6u<X>-linux-i586-rpm.bin, kde <X> je číslo posledního update, aktuálně <X>=19) a http://java.sun.com/javase/downloads/index_jdk5.jsp pro JDK 5.0. Variantu JDK od Sun-u (naproti OpenJDK) jsem zvolil na základě dokumentace JBoss AS a také zkušeností některých administrátorů. Nainstalujeme Sun JDK

[root@nps1:~]$ chmod 755 jdk-6u19-linux-i586-rpm.bin
[root@nps1:~]$ ./jdk-6u19-linux-i586-rpm.bin

Můžeme ověřit, že se JDK podařilo naistalovat (instaluje se do /usr/java)

[root@nps1:~]$ java -version
java version "1.6.0_19"
Java(TM) SE Runtime Environment (build 1.6.0_19-b04)
Java HotSpot(TM) Client VM (build 16.2-b04, mixed mode, sharing)

Vytvoříme si symbolické odkazy na javu z důvodu přehlednosti a případných pozdejších změn v administraci JBoss AS (např. změna verze javy, více verzí javy, atd.)

[root@nps1:~]$ mkdir -p /opt/java
[root@nps1:~]$ ln -s /usr/java/jdk1.6.0_19 /opt/java/jdk

Dále budeme potřebovat nastavit hodnoty proměnných pro javu. Můžeme si například v /etc/profile.d/ vytvořit skript java.sh s následujícím obsahem:

[root@nps1:~]$ vim /etc/profiles.d/java.sh
########################
# JAVA variables setup #
########################

JAVA_HOME=/opt/java/jdk
PATH=$PATH:$JAVA_HOME/bin

export JAVA_HOME PATH

přidáme právo pro spouštění a vyexportujeme pro aktualizaci proměnných:

[root@nps1:~]$ chmod 755 /etc/profile.d/java.sh
[root@nps1:~]$ sh /etc/profile.d/java.sh

Instalace JBoss AS

V zásadě máme dvě možnosti instalace. Kompilace ze zdrojových kódů nebo instalace z binárních balíčků. Já jsem zvolil instalaci z binárních balíčků. Balíček si můžeme stáhnout ze stránek projektu na adrese http://www.jboss.org/jbossas/downloads.html. Verzi jsem zvolil 5.1.0 (poslední stable release, verze 6.0.0 byla v té době ve druhé milestone verzi). Vytvoříme si cílový adresář pro JBoss AS, do kterého rozbalíme získaný balíček JBoss AS (zip soubor, jboss-5.1.0.GA.zip)

[root@nps1:~]$ mkdir -p /srv
[root@nps1:~]$ unzip -d /srv jboss-5.1.0.GA.zip

Vytvoříme symlink na kořenový adresář JBoss AS

[root@nps1:~]$ ln -s /srv/jboss-5.1.0.GA /opt/java/jboss

Stejně jako v případě javy, budeme potřebovat nastavit hodnoty proměnných pro JBoss AS. Vytvoříme si tedy skript jboss.sh v /etc/profile.d s následujícím obsahem:

[root@nps1:~]$ vim /etc/profile.d/jboss.sh
############################
# JBoss AS variables setup #
############################

JBOSS_HOME=/opt/java/jboss
PATH=$PATH:$JBOSS_HOME/bin

export JBOSS_HOME PATH

upravíme práva skriptu a aktualizujeme proměnné

[root@nps1:~]$ chmod 755 /etc/profile.d/jboss.sh
[root@nps1:~]$ sh /etc/profile.d/jboss.sh

V této chvíli máme již všechno připraveno pro spuštění JBoss AS. Aplikační server spouštíme skriptem run.sh, který se nachází v $JBOSS_HOME/bin

[root@nps1:~]$ sh $JBOSS_HOME/bin/run.sh

což odpovídá spouštění s konfigurací

[root@nps1:~]$ sh $JBOSS_HOME/bin/run.sh -c default -b 127.0.0.1

Dostupnost si ověříme na URL http://localhost:8080. JBoss AS předvoleně poslouchá jenom na localhost (127.0.0.1). Parametrem -c spouštěcího skriptu určujeme konfiguraci aplikačního serveru. Jednotlivé konfigurace se nacházejí v adresáři $JBOSS_HOME/server. Stručný popis několika základních konigurací nacházejících se v instalačním balíčku je možno najít v online dokumentaci. Parametrem -b určujeme rozsah adres, ze kterých je možno přistupovat na server. Chceme-li povolit přístup na server z libovolné adresy a s konfigurací default, spustíme server příkazem

[root@nps1:~]$ sh $JBOSS_HOME/bin/run.sh -c default -b 0.0.0.0

Aby jsme nespouštěli JBoss AS pod uživatelem root, vytvoříme si pro tuto službu samostatného uživatele a převedeme na něj vlastnická práva souborů aplikace

[root@nps1:~]$ useradd -r jboss
[root@nps1:~]$ chown -Rf /srv/jboss-5.1.0.GA

a nyní spustíme JBoss AS pod uživatelem jboss

[root@nps1:~]$ su - jboss -c $JBOSS_HOME/bin/run.sh

případně s konkrétnimi parametry.

JBoss SysV init

Aby jsme nemuseli spouštět manuálně, vytvoříme si pro JBoss AS spouštecí sysV init skript. Vzorové init skripty pro některé distribuce (Red Hat, Fedora, openSuSE) se již nacházejí v adresáři $JBOSS_HOME/bin. Vzhledem k distribuci použité na našem serveru jsem zvolil variantu pro distribuci Red Hat (soubor init_jboss_redhath.sh). Vzorový init skript zkopírujeme do adresáře /etc/init.d

[root@nps1:~]$ cp $JBOSS_HOME/bin/init_jboss_redhath.sh /etc/init.d/jboss

Náš nový init skript /etc/init.d/jboss ještě upravíme tak, aby odpovídal konfiguraci pro chkconfig. Do “hlavičky” skriptu doplníme potřebné direktivy (i s křížkem #)

[root@nps1:~]$ vim /etc/init.d/jboss
# chkconfig: 2345 85 15 #runlevely S-priorita K-priorita
# description: JBoss Application Server
# processname: jboss
# pidfile: /var/run/jboss.pid

Dále je potřeba v skriptu upravit hodnoty některých direktiv a jíné doplnit

JBOSS_HOME=${JBOSS_HOME:-"/opt/java/jboss"}
JBOSS_USER=${JBOSS_USER:-"jboss"} #uživatel pod kterým bude služba běžet
JAVAPTH=${JAVAPTH:-"/opt/java/jdk"}
JBOSS_CONF=${JBOSS_CONF:-"default"} #konfiguračny profil
JBOSS_HOST="0.0.0.0" #rozsah adres, ze kterých je možno přistupovat

Zaregistrujeme službu chkconfigem a aktivujeme

[root@nps1:~]$ chkconfig --add jboss (název služby je shodný s názvem init skriptu)
[root@nps1:~]$ chkconfig jboss on

Program chkconfig nám automaticky vytvoří symlinky v příslušných rcX profilech (adresáře /etc/rc[0..6].d) podle hodnot direktivy chkconfig v init skripte. Ověříme

[root@nps1:~]$ chkconfig --list jboss
jboss           0:off   1:off   2:on    3:on    4:on    5:on    6:off

Teď službu JBoss AS spustíme {zastavíme|restartujeme}

[root@nps1:~]$ /etc/init.d/jboss start {stop|restart}

nebo

[root@nps1:~]$ service jboss start {stop|restart}

SSL na straně JBoss AS

Základním případem použití SSL je povolení SSL na straně JBoss AS serveru. V takovém případě budeme přistupovat na web JBoss AS přes protokol HTTPS namísto HTTP. Vzhledem k tomu, že jde o vývojové prostředí, vystačíme si se self-signed certifikátem. Nejdříve si musíme vytvořit úložiště pro uchování páru klíčů a také certifikátu pro JBoss AS server. Pro tento účel použijeme aplikaci keytool, která se nachází v adresáři $JAVA_HOME/bin. Vytvoříme si úložiště (podle nové konvence se používá parametr -genkeypair), které umístíme někam do adresářové struktury dané konfigurace JBoss AS, například do $JBOSS_HOME/server/<konfigurace>/conf/ssl

[root@nps1:~]$ mkdir -p $JBOSS_HOME/server/<konfigurace>/conf/ssl
[root@nps1:~]$ $JAVA_HOME/bin/keytool -genkey -alias <alias> -keyalg RSA -keystore $JBOSS_HOME/server/<konfigurace>/conf/ssl/jboss.keystore

Při vytváření úložište budeme dotázáni na klíč pro úložiště. Dále si vygenerujeme certifikát pro JBoss AS server a uložíme ho do stejného úložiště jako klíče. Při geneování certifikátu budeme dotázáni na některé indetifikační údaje

[root@nps1:~]$ $JAVA_HOME/bin/keytool -export -alias <alias> -keystore $JBOSS_HOME/server/<konfigurace>/conf/ssl/jboss.keystore -file $JBOSS_HOME/server/<konfigurace</conf/ssl/jboss-server.cer

Nakonec upravíme connectory pro JBoss AS web tak, aby bylo možné přistupovat jenom přes HTTPS

[root@nps1:~]$ vim $JBOSS_HOME/server/<konfigurace>/deploy/jbossweb.sar/server.xml
...
<!-- A HTTP/1.1 Connector on port 8080
  <Connector protocol="HTTP/1.1" port="8080" address="${jboss.bind.address}" 
       connectionTimeout="20000" redirectPort="8443" />
-->
...
<!-- SSL/TLS Connector configuration using the admin devl guide keystore -->
<Connector protocol="HTTP/1.1" SSLEnabled="true"
       port="8443" address="${jboss.bind.address}"
       scheme="https" secure="true" clientAuth="false"
       keystoreFile="${jboss.server.home.dir}/conf/ssl/jboss.keystore" <!-- cesta k ůložišti cetifikátu a klíče -->
       keystorePass="<heslo_k_ulozisti≫" sslProtocol = "TLS" />
...

Dostupnost JBoss AS ověříme na URL https://<domena_JBoss_AS>:8443.