PSE_learning/parts/03_config.tex

477 lines
16 KiB
TeX
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

\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
\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{minted}[linenos,breaklines=true]{java}
CascadeType anschauen
@Entity
@Table(name = "user")
public class User extends AbstractEntity {
private static final long serialVersionUID = -7060150053795176748L;
@OneToMany(
mappedBy = "communityAdminUser",
cascade = {CascadeType.PERSIST, CascadeType.REFRESH}, // Persist Operation des User werden auch auf dessen communityAdminUser persistiert - und auch update
orphanRemoval = true //
)
\end{minted}
\section{Konfigurationsdateien}
\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}
........
</endpoint-config>
<client-config name="Standard-Client-Config"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:weld:3.0"/>
</profile>
<interfaces>
<interface name="management">
<inet-address value="${jboss.bind.address.management:127.0.0.1}"/>
</interface>
<interface name="public">
<inet-address value="${jboss.bind.address:127.0.0.1}"/>
</interface>
</interfaces>
<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
<socket-binding name="management-http" interface="management" port="${jboss.management.http.port:9990}"/>
<socket-binding name="management-https" interface="management" port="${jboss.management.https.port:9993}"/>
<socket-binding name="ajp" port="${jboss.ajp.port:8009}"/>
<socket-binding name="http" port="${jboss.http.port:8080}"/>
<socket-binding name="https" port="${jboss.https.port:8443}"/>
<socket-binding name="txn-recovery-environment" port="4712"/>
<socket-binding name="txn-status-manager" port="4713"/>
<outbound-socket-binding name="mail-smtp">
<remote-destination host="localhost" port="25"/>
</outbound-socket-binding>
</socket-binding-group>
</server>
\end{minted}
\end{code}
\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}
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="primary">
<jta-data-source>java:jboss/datasources/psoeDS</jta-data-source>
<properties>
<!-- Properties for Hibernate -->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
<property name="hibernate.enable_lazy_load_no_trans" value="true" />
<!--
SQL stdout logging
-->
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="false"/>
<property name="use_sql_comments" value="true"/>
<!--
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
-->
</properties>
</persistence-unit>
</persistence>
\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}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
<!-- Security roles -->
<security-role>
<description>administrators</description>
<role-name>ADMIN</role-name>
</security-role>
<!-- Security constraints -->
<security-constraint>
<web-resource-collection>
<web-resource-name>admin area</web-resource-name>
<url-pattern>/admin/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>ADMIN</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<realm-name>pse</realm-name>
<form-login-config>
<form-login-page>/login.xhtml</form-login-page>
<form-error-page>/login.xhtml</form-error-page>
<!-- <form-error-page>/loginerror.xhtml</form-error-page> -->
</form-login-config>
</login-config>
</web-app>
\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}
\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.
\section{Fehler im Projekt}
\subsection{Return null}
Anstatt von Null einfach eine Leere Liste bzw. ein default Objekt (oder new <Object>) 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}
\subsection{Testpyramide}
\begin{figure}[h]
\centering
\includegraphics[width=0.9\linewidth]{pics/testpyramide}
\caption{}
\label{fig:testpyramide}
\end{figure}
\subsection{Unit}
\subsection{Integration}
\subsection{Selenium bzw. GUI}