\part{Allgemeines \& Config} \section{Logging} \subsubsection{Vorteile Logging mittels Framework (z.B.: log4j)} \begin{itemize} \item Nutzt ein einheitliches Format / Konventionen \item logging kann optional an und ausgeschalten werden \item durch verschiedene Log-level können Logs gefiltert erstellt werden \item Layout für Ausgabe kann zentral definiert/geändert werden \end{itemize} \section{Annotationen} \begin{itemize} \item @MappedSuperclass \begin{itemize} \item ist im Hybernate Framework \item eine Klasse durch die gemeinsame Felder definiert werden. \item definiert eine abstrakte Superklasse \end{itemize} \item @Produces \begin{itemize} \item kommt während deployment, markiert Factory Method damit man nicht direkt auf die Klasse zugreifen muss \end{itemize} \item @Typed \begin{itemize} \item zeigt die Vererbung Wieso bei uns allein stehend? \end{itemize} \item @Named \begin{itemize} \item Zeigt bei Mehrdeutigkeit das richtige Objekt mit dem Namen \end{itemize} \item @Resource \begin{itemize} \item fast wie Dependency Injection \item damit weiß der Container wie er das Annotierte Feld instanzieren muss \end{itemize} \item @Stateless \begin{itemize} \item speichert den Client Status nicht \end{itemize} \item @Entity \begin{itemize} \item Data Access Layer \end{itemize} \item @Table \begin{itemize} \item Tabellenname im SQL \end{itemize} \item @Column \begin{itemize} \item SQL-Spalten nullable=false \end{itemize} \item @OneToMany \item @JoinColums \begin{itemize} \item welche Spalten zusammen gehören FK \end{itemize} \item @OneToMany \begin{itemize} \item auf anderen Seite \end{itemize} \item @ApplicationScoped \begin{itemize} \item lebt die ganze Applikation lang, wird einmal gemacht. \end{itemize} \item @PersistenceContext \begin{itemize} \item persistance.xml auslesen für Treiber und andere JPA Geschichten + Data Source. Entity Manager \end{itemize} \item @Id \begin{itemize} \item das ist die id \end{itemize} \item @GeneratedValue \begin{itemize} \item Wert kommt aus der DB \end{itemize} \item @Local \begin{itemize} \item Klasse für lokale Aufrufe. \end{itemize} \item @Remote \begin{itemize} \item interprozessaufrufe. RMI \end{itemize} \item @ApplicationException \begin{itemize} \item Rollback wenn so eine Exception kommt, Nachricht zum Client. \end{itemize} \end{itemize} \subsection{Annotationen - Details} \begin{itemize} \item CascadeType.Remove löscht die damit Annotierte Verknüpfung mit \item dies geht auch rekursiv in der kompletten Datenbank \item CascadeType.Remove und orphanRemoval=true ist equivalent \end{itemize} \begin{minted}[linenos,breaklines=true]{java} ... @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "documentlibrary_id") private Documentlibrary documentlibrary; @Column(nullable = false, unique = true) private String name; ... public enum CascadeType { /* Cascade all operations / ALL, /* Cascade persist operation / PERSIST, /* Cascade merge operation / MERGE, /* Cascade remove operation / REMOVE, /* Cascade refresh operation / REFRESH, */ DETACH ... } ) \end{minted} \section{Konfigurationsdateien} \subsection{persistence.xml} Die Datei \emph{persistence.xml} ist der zentrale Bestandteil der Persistierungs-Konfiguration. Folgende Dinge können konfiguriert werden: \begin{itemize} \item SQL dialect \item the persistence provider that shall be used at runtime \item the data source you want to use to connect to your database \item several provider-specific configuration parameters \end{itemize} \begin{code} \captionof{listing}{persistence.xml} \begin{minted}[linenos,breaklines=true]{xml} java:jboss/datasources/psoeDS \end{minted} \end{code} \begin{figure}[!htp] \centering \includegraphics[width=0.7\textwidth]{pics/ConfigFiles.png} \end{figure} \subsection{web.xml} \begin{itemize} \item konfiguriert den Java Webserver (Wildfly - JBOSS) \item Einbindung des Faces-Servlet (FrontController - Implementierung, Zugriffskontrolle, Rollenkonfiguration) \item befindet sich im Ordner \textbf{src/main/webapp/WEB-INF/web.xml} \end{itemize} \begin{minted}[linenos,breaklines=true]{xml} Faces Servlet javax.faces.webapp.FacesServlet 1 Faces Servlet *.xhtml administrators ADMIN admin area /admin/* ADMIN FORM pse /login.xhtml /login.xhtml \end{minted} \subsection{pom.xml} \begin{itemize} \item \textit{resources}-plugin (bindet die Serverressourcen ein - Ordner \textit{configuration} im Projekt - z.B. \textit{standalone-psoe.xml}) \item Wildfly (JBoss)– Webserver \begin{multicols}{2} \begin{enumerate} \item Compile \item Surefire (unitTests) \item Packaging - war file erstellen \item Wildfly - fressen und deployen \item Failsafe IT-test \item MVN site \item Gui test \end{enumerate} \end{multicols} \item Primeface = jsf Framework \item Jacoco = test Coverage \item Slf4j = logger \item Jaxb – xml \item Cdi = context dependancy injection \end{itemize} \subsubsection{Aufbau pom.xml} \begin{figure}[h] \centering \includegraphics[width=0.3\linewidth]{pics/pom-structure} \includegraphics[width=0.3\linewidth]{pics/pom-properties} \label{fig:pom} \end{figure} \subsection{standalone-psoe.xml} Wird ein JBoss Applikationsserver im \emph{standalone}-Modus betrieben, läuft jede Instanz in einem eigenen Prozess. Diese Datei ist eine Java Enterprise Edition 6 zertifizierte Web-Profil Konfiguration welche alle benötigten Technologien (z.B. Extensions von JBoss, Datasources etc.) definiert. JBoss EAP benutzt standardmäßig die standalone.xml Konfigurationsdatei, kann aber auch unter Verwendung einer anderen gestartet werden. Abschnitte der standalone.xml \begin{itemize} \item extensions (z.B. diverse Wildfly Module) \item management (z.B. Access Control -> role-mapping) \item profile (z.B. JPA Subsystem) \item interfaces (z.B. \${jboss.bind.address:127.0.0.1}) \item socket-binding-group (z.B \${jboss.http.port:8080}) \item Rechte (Management-Realm) \item Datenbankzugriffsparameter \end{itemize} \begin{code} \captionof{listing}{standalone.xml (auszugsweise)} \begin{minted}[linenos,breaklines=true]{xml} ........ \end{minted} \end{code} \subsection{log4j.properties} textit{src/test/resources/log4j.properties} \section{Fehler im Projekt} \subsection{Return null} Anstatt von Null einfach eine Leere Liste bzw. ein default Objekt (oder new ) zurückgeben \subsection{Exception nicht gefangen} \begin{minted}[linenos,breaklines=true]{java} package at.fhj.swd.psoe.service.impl; ... @Override public void removeCommunityByAdmin(CommunityDTO communityDTO) { Community community = communityDAO.findById(communityDTO.getId()); String errorText; try { communityDAO.removeCommunityByAdmin(community); } catch (DaoException e) { errorText = "Error removing community"; logger.error(errorText, e); throw new ServiceException(errorText); } } \end{minted} \subsection{Destructive Wrapping - Logging fehlt - Information geht verloren} \begin{minted}[linenos,breaklines=true]{java} package at.fhj.swd.psoe.service.impl; ... @Override public CommunityDTO updateCommunityEnabled(String adminUserId, String communityName, boolean isEnabled) { String errorText = ""; try { boolean hasPermission = false; User adminUser = userDao.getByUserId(adminUserId); Community community = communityDAO.getByName(communityName); for (Role r : adminUser.getRoles()) { if (r.getname().equals("ADMIN") || r.getname().equals("PORTALADMIN")) { hasPermission = true; } } if (hasPermission || adminUser.getUserId().equals(community.getCommunityAdminUser().getUserId())) { community.setIsEnabled(isEnabled); communityDAO.update(community); } else { errorText = "No Permission to update community"; throw new AuthenticationException(errorText); } return CommunityMapper.toDTO(community); } catch (DaoException e) { errorText = "Error updating community enabled"; throw new ServiceException(errorText); } catch (AuthenticationException e) { throw new ServiceException(errorText); } catch (Throwable e) { errorText = "Unknown error updating community enabled"; throw new ServiceException(errorText); } } \end{minted} \subsection{Logger mit falschem Parameter} \begin{minted}[linenos,breaklines=true]{java} package at.fhj.swd.psoe.service.impl; ... @Stateless public class DepartmentHierarchyServiceImpl implements DepartmentHierarchyService, Serializable { private static final long serialVersionUID = -2467949382018996094L; private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class); ... \end{minted} \subsection{Fehlendes Exception Handling} \begin{minted}[linenos,breaklines=true]{java} package at.fhj.swd.psoe.service.impl; ... @Local(MessageService.class) @Remote(MessageServiceRemote.class) @Stateless public class MessageServiceImpl implements MessageService, MessageServiceRemote, Serializable { ... @Override public MessageDTO getByMessageId(long id) { Message message = messageDAO.getById(id); if (message == null) { return null; } return MessageMapper.toDTO(message); } @Override public MessageDTO updateByAdmin(long id, String content, Date changed) { Message message = messageDAO.getById(id); message.setContent(content); message.setEditedByAdmin(changed); return MessageMapper.toDTO(messageDAO.update(message)); } @Override public MessageDTO updateByUser(long messageId, String content, Date changed) { Message message = messageDAO.getById(messageId); message.setContent(content); message.setEditedByUser(changed); return MessageMapper.toDTO(messageDAO.update(message)); } \end{minted} \subsection{Exception werfen und gleich wieder fangen} \begin{minted}[linenos,breaklines=true]{java} ... public class MessageServiceImpl implements MessageService, MessageServiceRemote, Serializable { private static final long serialVersionUID = 6768291437557855130L; ... // nicht optimal, da die IllegalArgumentException gleich wieder gefangen wird // überdies wird alles andere nicht gefangen @Override public void deleteMessage(long id) { try { Message message = messageDAO.getById(id); if (message == null) { throw new IllegalArgumentException("Message cannot be empty"); } messageDAO.delete(message); logger.info("Message deleted successfully"); } catch (IllegalArgumentException ex) { String errorMsg = "Could not delete the message (illegal argument)"; logger.error(errorMsg, ex); throw new ServiceException(errorMsg); } } //---------- besser wäre package at.fhj.swd.psoe.service.impl; ... // erst loggen, dass man in die Methode gekommen ist // wenn userDTO null ist wird IllegalArgument geworfen und das außerhalb des try catch blocks // erst wird die DaoException gefangen und anschließend alle anderen // Stacktrace wird geloggt und jeweils die ServiceException weitergegeben @Override public void saveUser(UserDTO userDTO) { logger.debug("UserService saveUser() called with parameter: '{}'", userDTO); if (userDTO == null) { throw new IllegalArgumentException("userDTO is null"); } try { User user = (userDTO.getId() == null) ? new User() : userDao.getById(userDTO.getId()); userDao.update(UserMapper.toEntity(userDTO, user)); } catch (DaoException e) { logger.error("Error saving user", e); throw new ServiceException("Error saving user"); } catch (Throwable e) { logger.error("Unknown error saving user", e); throw new ServiceException("Unknown error saving user"); } } \end{minted} \section{Tests} \begin{figure}[h!] \centering \includegraphics[width=0.7\linewidth]{pics/test-architecture} \caption{} \label{fig:test-architecture} \end{figure} \begin{itemize} \item Ziel der Tests ist es, 100\% Codecoverage im Service-Layer zu erreichen (Anforderung für unser Projekt), \item Private und Protected-Methoden müssen per se nicht getestet werden (Rule of Thumb),Wege zum erfolgreichen Test: \begin{itemize} \item Access-Modifier der Methoden auf public (public static) ändern, \item Extrahieren der Methoden in neue Klassen, \item Notfalls Package-Sichtbarkeit in Methode, wenn diese privat bleiben muss, Tests aber unbedingt notwendig sind. \end{itemize} \end{itemize} \subsection{Testpyramide} \begin{figure}[ht!] \centering \includegraphics[width=0.9\linewidth]{pics/testpyramide} \caption{} \label{fig:testpyramide} \end{figure} \subsection{Unit} \begin{itemize} \item Im Build-Cycle werden zuerst Unit-Tests ausgeführt, \item sie befinden sich im Projekt im Ordner \textit{src/test/at/fhj/swd/psoe/service/*Test.java}, \item ein Unit-Test muss jede Methode der Service-Klasse testen \item Mockito imitiert die Methoden, die von der Datenbank abhängen, da diese zu diesem Zeitpunkt noch nicht zur Verfügung steht (Build-Cycle => Compile - Unit-Tests) \item Im Unit-Test wird jeweils die kleinste Einheit getestet \item Exceptions einfach mit Mockito werfen und testen, ob sie geworfen wurden \end{itemize} \begin{minted}[linenos,breaklines=true]{java} @Test(expected = ServiceException.class) public void testGetCommunityByUserMessagesDAOException() { Mockito.when(this.userPrincipal.getId()).thenReturn(-1L); Mockito.when(this.communityService.loadAllCommunitiesByUser(-1L)).thenThrow(new DaoException(new RuntimeException())); this.activitystreamService.getCommunityByUserMessages(); } \end{minted} \begin{minted}[linenos,breaklines=true]{java} @Test public void getDocumentsFromCommunity_ShouldReturnDocuments() { Community community = Mockito.mock(Community.class); Mockito.when(community.getId()).thenReturn(DOCUMENTID); Documentlibrary doclib = Mockito.mock(Documentlibrary.class); Mockito.when(community.getDocumentlibrary()).thenReturn(doclib); Mockito.when(doclib.getCommunity()).thenReturn(community); User user = Mockito.mock(User.class); Mockito.when(communityDAO.findById(community.getId())).thenReturn(community); List documents = prepareTwoDocuments(doclib,user); Mockito.when(documentDAO.findByCommunity(community)).thenReturn(documents); List documentsDTO = documentService.getDocumentsFromCommunity(community.getId()); Assertions.assertThat(documentsDTO) .usingElementComparatorIgnoringFields("user") .containsExactlyInAnyOrder(DocumentMapper.toDTO(documents.get(0)), DocumentMapper.toDTO(documents.get(1))); } \end{minted} \subsection{Integration Tests}%TODO Wolfimajer - moch fertig , bi \begin{itemize} \item testen alle Komponenten mit (Datenbank) \item werden im Build-Cycle erst nach dem Deployen des WAR-Files ausgeführt (laufende Applikation) \item In unserem Projekt kommt das Plugin \textit{failsafe} zum Einsatz \item Datenbankscripts werden separat zu den anderen Scripts ausgeführt (Datenbank vorbereiten und auf den alten Stand zurückbringen) \item \begin{figure}[h] \centering \includegraphics[width=0.7\linewidth]{pics/test_structure} \caption{} \label{fig:teststructure} \end{figure} \end{itemize} \subsection{GUI-Test} \subsubsection{Page Object Pattern} \begin{itemize} \item stellt Screens der Web-App als Reihe von Objekten dar \item tatsächlich werden nicht alle Seiten sondern wesentliche Elemente in Objekte gekapselt \item eine HTML Seite wird so mitunter mit mehreren Objekten dargestellt (z.B. Header und Footer Page Object) \item Das Page Objekt Design eignet sich besonders gut um Selenium Tests umzusetzen \item Mittels der Page Objekte kann HTML Code verändert werden (Verkapselung) \item ermöglichen die Modellierung der Benutzeroberfläche für Tests \item reduziert Code duplication \item verbessert Testwartbarkeit und macht Tests robuster \end{itemize} \subsection{Page Object Pattern lt. Zahnlücke} \begin{itemize} \item Trennung zwischen Testmethode und Page Code \item Je Page eine Klasse mit Services / Operationen \item Return einer Operation ist ein PageObject \item Einfachere Wartbarkeit (Kapselung in PageObject) \end{itemize} \begin{figure}[!htp] \centering \includegraphics[width=0.8\textwidth]{pics/page-object.png} \end{figure} \subsection{Beispiel aus dem Projekt} \subsubsection{Integration GUI Test mit Selenium} \begin{minted}[linenos,breaklines=true]{java} package at.fhj.swd.psoe.gui.pages; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import java.util.List; public class DocumentsListPage extends AbstractPage { private List list; public DocumentsListPage(WebDriver driver) { super(driver); } public List getList() { list = this.getDriver().findElements(By.xpath("//*[@id=\"documents:comdoctable_data\"]")); return list; } } \end{minted} \subsubsection{Durch Selenium getestetes Page Objekt} \begin{minted}[linenos,breaklines=true]{java} package at.fhj.swd.psoe.gui; import at.fhj.swd.psoe.JdbcTestHelper; import at.fhj.swd.psoe.gui.pages.*; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.sql.SQLException; import java.util.stream.Collectors; public class ShowDocumentsListITCase extends AbstractChromeTest { final private String roleName = "ADMIN"; private LoginPage loginPage; private WelcomePage welcomePage; private DocumentsListPage documentsListPage; private String baseUrl = "http://localhost:8080/chr-krenn-fhj-ws2018-swd16-pse"; private static final JdbcTestHelper JDBC_HELPER = new JdbcTestHelper(); @Before @Override public void setUp() { super.setUp(); JDBC_HELPER.executeSqlScript( "src/test/resources/sql/DocumentServiceITCase-addDocument.sql"); loginPage = new LoginPage(this.driver, baseUrl, 60); welcomePage = loginPage.login("testdocument@swd.com", "admin"); CommunitiesPage communitiesPage = welcomePage.openCommunitiesPage(); CommunityPage communityPage = communitiesPage.openCommunityPage(); documentsListPage = communityPage.openDocumentListPage(); } @Test public void testTwoDocumentsListed() { String content = documentsListPage.getList().stream().map(x -> x.getText()).collect(Collectors.joining()); Assert.assertTrue(content.contains("documentuser123")); Assert.assertTrue(content.contains("DocumentITCase1")); Assert.assertTrue(content.contains("DocumentITCase2")); } @After @Override public void tearDown() { super.tearDown(); JDBC_HELPER.executeSqlScript( "src/test/resources/sql/DocumentServiceITCase-deleteDocument.sql"); } } \end{minted} \section{Toni FRAAGNAA} Den Code durchgehen - was statt null - könnte leere Liste sein, return null sollte aber auch okay sein welche Exception - logger ok? ob ein Throw im try Block ok ist - sollte okay sein, da wenn nicht im try-Block, erfolgt kein Mapping als DAO-Exception. \begin{minted}[linenos,breaklines=true]{java} @Override public List findByUser(User user) { logger.debug("dao: documents findByUser"); try { if(user == null) throw new IllegalArgumentException("user must not be null"); return entityMangaer.createQuery("from Document d where d.user.userId = :userid", Document.class).setParameter("userid", user.getUserId()).getResultList(); } catch(NoResultException noresex) { logger.info("dao: findByUser no documents found"); return null; } catch (Exception e) { throw new DaoException("error finding documents of user",e); } } // throw in try - passt @Override public void delete(Document document) { logger.debug("dao: delete document"); try { if(document == null) throw new IllegalArgumentException("document must not be null"); if(!entityMangaer.contains(document)) { document = entityMangaer.merge(document); } entityMangaer.remove(document); } catch (Exception e) { throw new DaoException("error deleting document",e); } } // ist loggen ohne stacktrace ok? - Stack-Trace gehört eigentlich dazu @Override public TimeRecording getTimeRecordingByTask(Task task) { TimeRecording result = new TimeRecording(); try { result = entityManager.createQuery("select t from TimeRecording t where t.task.id = :id", TimeRecording.class) .setParameter("id", task.getId()).getSingleResult(); } catch (NoResultException e) { logger.warn("Not Possible to find TimeRecording with task id " + task.getId()); } return result; } // UserDTO - @XmlElement @XmlElement - sichtbar in XmlSerializerTest - für den automatischen Import der User via XML public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; } // wos is produces @Override @Produces - ist eine Factory-Methode - kommt während dem Deployment und erstellt das Objekt @Named("userPrincipal") @SessionScoped public UserPrincipal getUserPrincipal() { String principalName = sessionContext.getCallerPrincipal().getName(); logger.info(principalName); try { User user = userDao.getByEmail(principalName); return new UserPrincipal(user.getId(), user.getUserId(), user.getEmail(), user.getFirstname(), user.getLastname()); } catch (DaoException e) { logger.error("Error loading user '{}'", principalName, e); throw new ServiceException("Error loading user"); } catch (Throwable e) { logger.error("Unknown error loading user '{}'", principalName, e); throw new ServiceException("Unknown error loading user"); } // @Typed - zeigt bei Mehrdeutigkeit die Vererbung (z.B. implementiert Interface und leitet von Klasse ab - muss aber in Klammer immer mitgegeben werden, wovon Java Server Beans dann die Ableitung macht) // warum immer mappedBy Mehrzahl @ManyToMany(mappedBy = "businessTrips") - Name vom Feld in der Entity, die verbunden wird, da ManyToMany, ist es egal, bei welcher Tabelle // dependency Injection - wird schon im Skript erklärt // braucht man im Controller (ViewHelper) überhaupt noch Exception Handling - es passiert nur Nutzereingabenvalidierung und die Fehlermeldung kann nicht weitergeworfen werden - es wird nur mehr in den Logger geschrieben //müssen wir die Folien genau beherrschen (Stubs vs. Mocks?) Nein //Bei welchem Goal wird was mitausgeführt? IT-Test bei mvn wildfy:run? //Woher weiß PrimeFaces, wie es zum Ordner web mit den Controllern kommt? //MessagePrincipal - @Typed - bereits oben erklärt //Wie löst Maven Abhängigkeiten zu Libraries auf? Es wirdim Maven-Repository nach dem Package gesucht und dort nach der Version, die in der Dependency angegeben wurde. Wird die Library nicht gefunden, muss ein alternatives Repo angegeben werden. WICHTIG: niemals Libraries mit Plugins verwechseln (Plugin ist eine Erweiterung der Maven-Funktionalität, Library ist ein bestehender Java-Code, der verwendet werden kann) \end{minted} \section{Frageart Prüfung} Welche Fehler können bei Exception-Handling vorkommen in unserem Projekt?? – wie funktioniert es grundsätzlich in unserem Code DocumentDAO – DocumentService – DocumentController – so sollte Exception-Handling implementiert warden DAO wirft Exception – im ServiceLayer wird dies gefangen und der Stack-Trace wird im weggeloggt und eine benutzerfreundliche Fehlermeldung wird ausgegeben (Destructive Wrapping). Alle Patterns, die vorkommen – praktische Beispiele aus dem Code Was sind JavaBeans? Wie funktioniert das Konzept? Wie wird es genau implementiert? NamedBean, TypedBean etc.