SAPUI5 Custom Controls – Teil 1

SAPUI5 Custom Controls – Teil 1

SAPUI5 Custom Controls Teil 1

Um SAPUI5 Custom Controls besser erklären zu können haben wir uns für eine mehrteilige Blog-Reihe entschieden. Wenn der SAPUI5 Standard nicht mehr ausreicht, müssen Custom Controls entwickelt werden. Alles zum Thema Custom Control Development erfahren Sie hier.

SAPUI5 Custom Controls Teil 1 Inhalt:

Das Konzept

Der Aufbau

Properties

Aggregations

Associations

Events

Zusätze

Coding

Render Manager

 

Welches Konzept steckt hinter SAPUI5 Custom Controls?

In SAP UI5 ist es möglich, dass Sie bestehende Controls erweitern. Wenn die normalen Funktionalitäten eines Controls nicht mehr ausreichen. Ebenso lassen sich ganz neue Controls erstellen, um den wachsenden Anforderungsschwierigkeiten gewachsen zu sein.

Leider weichen Sie dann  jedoch vom Fiori-Standard ab und ist nicht mehr Guideline-Konform. Jedoch kann es trotzdem sein, dass man eine Aufgabenstellung zu bewältigen hat, die mit dem Standard nicht abzubilden ist.

Genau hier tritt das Custom-Control-Konzept ins Spiel.

 

Wie ist der Aufbau von SAPUI5 Custom Controls?

Grundsätzlich ist ein UI5 Control nichts anderes als ein Modul. Es wird zur Laufzeit dynamisch nachgeladen. Ein solches Modul können Sie mit sap.ui.define erstellen und anschließend angesprechen.

Des Weiteren wird ein UI5 Control immer als HTML-Element gerendered. Wie genau das aussieht, wird immer im Control-spezifischen render-Methode über den RenderManager definiert. Mit dieser Methode können Sie das Custom Control in die HTML-Elemente umwandeln. So möchte es der Entwickler gerne haben. Es wird zum Beispiel beim Rendern einer sap.m.Table eine HTML-table mit td- und tr-Elementen in das DOM eingetragen und mit SAP-spezifischen CSS-Klassen gestyled.

Beim Aufbau von Eigenschaften eines Controls werden Sie immer in seinen Metadaten definieren. Ein Control hat ein dementsprechendes metadata-Object. Dort können Sie  Properties, Events, Aggregations und Associations definieren. Diese können dann in der View und über den Controller angesprochen werden.

Dies sieht wie folgt aus:

 

 

Sobald ich die Methode extend von dem sap.ui.core.Control aufrufe, erstelle ich ein neues Control. Wenn ich das extend von einem bestehenden Control aufrufen, z.B. dem sap.m.Button, würde ich dieses erweitern.

Nach dem ich nun ein neues Control erstellt habe, kann ich die Metadaten definieren:

 

Properties im SAPUI5 Custom Control

Control-Properties bekommen ein Object zugewiesen, in dem Sie den Datentyp zuweisen und gegebenenfalls einen Default-Wert hinterlegen.

Folgende Einstellungen kann ich also vornehmen:

  • type – Default type ist string.
  • defaultValue

Zulässige Datentypen für die type-Property sind:

  • string
  • int
  • float
  • string[]

Sie können auch eigene Konfigurationsproperties innerhalb meiner Property erstellen und diese dann zur Datenverarbeitung nutzen.

Aggregations

Aggregations können Sie mit 3 verschiedenen Konfigurationen erstellen.

  • type – Default type ist sap.ui.core.Control.
  • multiple – Default ist true.
  • singularName
  • visibility 

Type gibt an, welches Control Sie gerne aggregieren möchten. Manchmal sind solche Aggregation-Controls nicht Module die von sap.ui.core.Control erben, sondern von der abstrakten Klasse sap.ui.core.Element. Sie haben daher keinen eigenen Renderer. Das Rendering von diesen Controls übernimmt das Control, das diese Elemente aggregiert. Mein Custom Control kann natürlich auch andere Custom Controls aggregieren.

Mit multiple: true kann ich sagen, ob meine Aggregation 0 bis N Controls aggregieren kann, oder ob nur ein Control in meiner Aggregation zugelassen wird.

Die Property singularName gibt einen String an, der für automatisch generierte Methoden eingefügt werden soll, um z.B. ein neues Item in die Items-Aggregation einzufügen.

Per visibility-Property lege ich fest, ob meine Aggregation in der View benutzt werden kann und ob sie für den Entwickler sichtbar sein sollte. Wenn die visibility auf Verbergen sein sollte, dann muss die Value „hidden“ vergeben werden.

Associations

Mit Associations können Controls miteinander verknüpft werden. Es kann eine Abhängigkeit erstellt werden. Das bekannteste Beispiel ist die labelFor-Property/Association vom sap.m.Label.

Associations haben den gleichen Konfigurations-Aufbau wie Aggregations. Sie haben daher auch:

  • type
  • multiple
  • singularName
  • visibility

Events

Controls können Eventhandler für bestimmte HTML-Events definieren. Für jedes selbstdefinierte Event werden automatisch folgende Funktionen erstellt:

  • attach<Name>
  • detach<Name>
  • fire<Name>

Somit würde ich z.B. für mein Custom Event saveForm die Funktionen attachSaveForm, detachSaveForm und fireSaveForm automatisch generiert bekommen.

Sobald ich ein Event erstellt habe, muss ich nur mehr den dementsprechenden HTML-Eventhandler definieren und dort die fire-Methode meines Events aufrufen. Für die Event-Handler-Implementierung erstelle ich innerhalb meines Controls eine neue Methode, die genau so heißt wie das dementsprechende HTML-Event.

Events können auch Parameter definieren, die ich bei einem fireEvent befülle.

myEvent: {

    parameters: { wasClicked: {type: „boolean“}}

}

Zusätze

Zusätzlich zu den 4 Basis-Konfigurationen stehen noch mehr Optionen zur Verfügung.

So kann ich

  • dnd – Drag and Drop
  • designtime – Design

zusätzlich definieren und konfigurieren. Auch kann ich das Metadaten-Objekt um eigene Einstellungen erweitern.

 

Coding

Die wichtigste Methode unseres Controls ist die onInit-Methode. Diese wird beim Initialisieren unseres Controls aufgerufen und dient mir als Startmöglichkeit.

Weitere Funktionen können ganz normal als Objekt-Methoden eingefügt werden. Hier sollte aber auf das Naming der Methoden geachtet werden. Damit es keine Überschneidungen zu automatisch generierten Methoden kommt.

Mit Hilfe der automatisch generierten Set– und Get-Methoden meiner Properties kann ich jetzt auf diese zugreifen.

 

Render Manager im SAPUI5 Custom Controls

Nachdem ich nun die Metadaten und das Coding fertig habe, muss ich das Control auch dementsprechend anzeigen.

Hierfür dient der Render Manager in der renderer-Methode meines Controls. Mit dessen Hilfe kann ich in das DOM eingreifen und mein Control platzieren. Der Render Manager kommt in der renderer-Methode meines Controls zum Einsatz. Benutzt wird normaler HTML-Code, um unser Control in das DOM zu bringen.

Normalerweise wird die renderer-Methode in ein eigenes File ausgelagert.

Was sind wichtigsten Render Manger Funktionen?

Die wichtigsten Funktionen des RM’s sind:

  • openStart – Erstellt ein neues öffnendes Element <…
  • openEnd – Schließt das öffnende Element <…>
  • close – Schließt das Element </…>
  • write – Schreibt HTML-Code
  • writeControlData – Schreib die Conrol-ID in das DOM
  • renderControl – Ruft die renderer-Methode eines aggregierten Controls auf
  • class – Fügt eine CSS-Klasse dem HTML-Element hinzu
  • attr – Fügt dem Element ein Attribut mit Value hinzu

Zusammenfassung

Wir sehen, dass das Erstellen eines Custom-Controls einige Möglichkeiten beinhaltet. Wir können Properties, Aggregations, Events und Associations definieren. Zusätzlich benötigen wir noch die renderer-Methode mit dem RenderManager, um unser Control in das DOM zu bringen und anzuzeigen.

Falls Fragen auftauchen, können sie gerne unten in den Kommentaren gestellt werden.  Ich freue mich auf Feedback.

Im nächsten Blog-Part 2 werden wir ein Beispiel-Control entwickeln und uns im Detail die Implementierung ansehen.

 

Git Tutorial 2 |Git Anleitung| Git Wissen| Git erklären Teil 2

Git Tutorial 2 |Git Anleitung| Git Wissen| Git erklären Teil 2

Git Tutorial: die wichtigsten Begriffe und Funktionen

Mit diesem Git Tutorial / Git Grundlagen wirst du zum Git Profi!

Mit dieser Git-Anleitung möchten wir dir zeigen, wie wichtig Git für Entwickler geworden ist. In dieser Blogreihe werden wir uns mit dem Thema Git beschäftigen und damit, warum das Arbeiten mit Git oder mit einem ähnlichen Tool für Entwickler fast schon unumgänglich ist.

Die folgenden Informationen werden auch, jedoch um einiges ausführlicher, in unserem – offiziell bei der SAP im Schulungskatalog gelisteten – Kurs HOUI5 behandelt.

Inhalt des Git Tutorials

Wir werden auf folgende Themen eingehen:

  • Einführung in die Welt von Git
  • Begrifflichkeiten und die wichtigsten Funktionen
  • SAPUI5-Entwicklung mit Hilfe von Git
    • Exkurs: LDAP-Anbindung und Git-Server-Anbindung an die SAP Cloud Platform

Zielsetzung

Unser Ziel ist es, dass du nach dem  Git Tutorial, die unten stehende Grafik interpretieren und mit Git die SAPUI5-Entwicklung optimieren kann.

Das Video zum Blog

Wichtig

Seitdem GitHub die Namenskonventionen umgestellt hat, heißt der Strang, der automatisch angelegt wird, nicht mehr „master“ bzw. „origin/master“, sondern „main“ und „origin/main“. Da die SAP WebIDE diese Änderung noch nicht erkennt, muss man das entweder in der WebIDE mitgeben oder in GitHub übersteuern.

Git Tutorial Part 2 – Inhaltsverzeichnis

Begrifflichkeiten:

  1. Repository
  2. Branch
  3. Commit
  4. Staging Area

Funktionen:

  1. Push
  2. Clone
  3. Fetch
  4. Merge
  5. Pull
  6. Best practice
  7. Rebase
  8. Reset (Hard Reset & Mixed Reset)

Über den Autor

Daniel Krancz

Daniel Krancz

SAP-Consultant / Software-Developer

Ich bin SAP-Berater und -Entwickler im SAPUI5/Fiori- und OData-Umfeld. Seit 2019 bin ich offiziell als externer Trainer bei SAP gelistet und halte Kurse (UX, S4, …) über SAP-Webentwicklungen und Cloud-Implementierungen im In- und Ausland. Seit 2021 bin ich SAP Press Autor beim Rheinwerk Verlag im Bereich SAP Mobile.

Begrifflichkeiten

Zu einem guten Git Tutorial gehört natürlich auch, dass erklären der wichtigsten Begrifflichkeiten und gängigsten Funktionen im Git-Umfeld.

Repository

Ein Repository kann mit einem einfachen Verzeichnis verglichen werden, in welchem weitere Verzeichnisse und Dateien abgelegt werden können. Ein Repository beinhaltet normalerweise ein Projekt bzw. ein Programm.

Es gibt eine Unterscheidung zwischen einem Remote (am Server oder in der Cloud) und einem Local (in der IDE) Repository.

Branch

Ein Repository beinhaltet mindestens einen Entwicklungsstrang, kann aber auch in mehrere Entwicklungsstränge unterteilt werden. Diese Stränge nennt man Branches.

Branches benutzt man entweder um Systemlandschaften (z.B. DEV, QAS und PRD) oder Feature-Entwicklungen (z.B. eine neue Funktionalität für Kunden) abzubilden. Die Unterteilung in den Landschaften ist üblicherweise am Remote Repository vertreten.

Je nachdem wo die Branches liegen, redet man von Remote und Local Branches.

Wenn man ein Remote Repository erstellt, so wird automatisch auch der erste Branch mit dem Namen „origin/master“ angelegt. Bei einem Local Repository heißt der erste Branch nur „master“.

Commit

Einen Commit kann man mit einem Snapshot, sprich mit einer Momentaufnahme, vergleichen. Diese Momentaufnahme beinhaltet die gesamten Änderungen seit dem letzten Commit. Diese Commits werden mit bestimmten Daten (Description, ID, Author, Timestamp, …) versehen und abgespeichert.

Ein Commit ist somit die kleinste „Einheit“ im Git-Umfeld.

Staging Area

In der Staging Area befinden sich alle Files, die seit dem letzten Commit editiert wurden. Das heißt, die Staging Area ist ein Ort für das Zwischenspeichern der Änderungen. Zusätzlich kann man auswählen, welche Änderungen aus der Staging Area man beim nächsten Commit mitnehmen möchte.

So könnte man X Änderungen aus der Staging Area auf Y Commits aufteilen und so einen sauberen Entwicklungsstrang abbilden.

Wie funktioniert Git, und welche Vorteile hat es für die SAP-Entwicklung?- Alles darüber findet Ihr in diesem Buch: Amazon oder Rheinwerk

Git und SAP - Versionsverwaltung und Transporte

Welche Funktionen gibt es in Git?

Mit diesem Git Tutorial, gehen wir ausserdem auf die gängigsten Funktionen ein, mit der man Repositories und Branches manipulieren kann. Wir werden uns nur die wichtigsten anschauen (Push, Fetch, Merge, Pull, Clone, Rebase), aber es gibt noch um einiges mehr (Cherry-Pick, Stashing, …).

Push

Wenn man in der IDE entwickelt und irgendwann einen Stand hat, den man mit anderen Entwicklern teilen möchte, dann muss man den letzten Commit mit einem Push in das Remote Repository bringen.

Da das Remote Repository am Anfang leer ist, kann der erste Entwickler, der z.B. das Rahmenprojekt aufgesetzt hat, seine Entwicklungen ohne Rücksicht pushen:

Später sollte vor jedem Push gecheckt werden, ob sich am Remote Repository etwas geändert hat. Wie das funktioniert, erfahren Sie weiter unten.

Clone

Es kann nur einen ersten bzw. initialen Push geben. Wenn also sich am Remote Repository bereits Daten befinden, dann muss ich das Ganze in meine lokale Umgebung bekommen.

Das funktioniert mit einem Clone:

Fetch

Mit einem Fetch kann ich die Änderungen, die seit meinem letzten Fetch gemacht wurden, in die lokale Umgebung laden. Einfacher ausgedrückt: Wir schauen nach, ob andere Entwickler was Neues gemacht haben.

Merge

Nur nachschauen, ob andere Entwickler etwas gemacht haben, reicht noch nicht ganz aus. Wir müssen die Datenstände zusammenführen und diesen Vorgang nennt man Merge.

Wir haben vorhin, als wir über Branches geredet haben, etwas verschwiegen. Die Namen für Branches sind nichts anderes wie Pointer bzw. Zeiger. Ein Zeiger auf den aktuellen Commit, sprich auf den letzten Snapshot.

Was bei einem Merge technisch passiert ist ganz einfach: Der Zeiger von meinem altem Commit, wird auf den neuen Commit gesetzt, welchen wir uns mit einem Fetch geladen haben. Dabei wird geschaut, ob sich Änderungen überschneiden. Wenn sich Änderungen überschneiden, gibt es Konflikte.

Auf Konflikte wird man immer hingewiesen und der Entwickler, der die Zusammenführung macht, kann entscheiden, welche Code-Passagen die Aktuellen sind. Ein Merge wird in einem eigenen Commit protokolliert.

Pull

Man sieht jetzt schon, dass man Fetch und Merge oft brauchen wird. Das haben sich auch die Entwickler von Git gedacht und deswegen die Funktion Pull ins Leben gerufen.

Pull führt zuerst einen Fetch und dann automatisch einen Merge aus:

Git Tutorial – Best practice

Einmalig:

  • Clone or Initial Commit and Push
    • wenn ich der erste bin = Push
    • App vom Remote Repository clonen

Wiederkehrend:

  1. Fetch: schauen ob sich was getan hat
  2. Merge: den Zeiger auf die aktuelle Version verschieben und die Datenstände zusammenführen (auf eventuelle Konflikte reagieren)
  3. Push: die aktuelle Version mit anderen Entwicklern teilen
  1. Pull: schauen ob sich was getan hat, den Zeiger auf die aktuelle Version verschieben und die Datenstände zusammenführen (auf eventuelle Konflikte reagieren)
  2. Push: die aktuelle Version mit anderen Entwicklern teilen

Wir kennen jetzt viele Begriffe und kennen auch schon die wichtigsten Funktionalitäten. Jetzt gehen wir nochmal auf die Branches ein und bringen all das Wissen mit, was wir gelernt haben.

Wir haben gesagt, dass auch am Remote Repository Branches erstellt werden können (z.B. P,Q,D), aber auch am Local Repository (z.B. Features).

Gehen wir von folgendem Beispiel aus: Es existiert bereits ein Repository und da wir jetzt auch im Entwicklerteam sind, können wir vom Remote Repository vom Entwicklungsbranch die aktuellen Stände clonen.

Wir machen sicherheitshalber einen Local Feature Branch wo wir einige Entwicklungen und auch Commits machen. Nach einigen Entwicklungstagen sind wir fertig und mergen lokal unsere Features mit dem lokalen Master.

Bevor wir unsere Änderungen mit anderen teilen können, müssen wir checken, ob andere Entwickler auch zwischenzeitlich gepusht haben und führen einen Fetch und danach Merge bzw. einen Pull aus.

Nachdem wir uns mit einigen Konflikten beschäftigen durften, können wir nun endlich unsere Änderungen pushen und mit anderen Entwicklern teilen.

Unser Administrator nimmt nach eigenem Ermessen die Datenstände und macht einen Merge zwischen D und Q und signalisiert somit dem Testteam, dass sie testen können.

Wenn die Tests abgeschlossen sind, dann kann der Administrator zwischen Q und P einen Merge durchführen. Der P-Branch wird dann entweder exportiert oder der Administrator deployed ihn direkt auf das Kundensystem und der Kunde hat nun eine neue Version der Applikation.

Rebase

Wenn wir uns die obenstehende Grafik genauer anschauen, dann werden wir erkennen, dass durch einen Merge immer ein „unnötiger“ Commit entsteht.

Durch Rebase können wir auch dieses – oft von Entwicklern empfundene – Designproblem lösen und einen sauberen Entwicklungsstrang ohne „Merge-Commits“ aufbauen.

Nehmen wir an, wir haben lokal einen Feature Branch gemacht. Dort haben wir fleißig entwickelt und möchten jetzt unsere Änderungen wieder auf den Master Branch bekommen:

Wenn wir jetzt ein Rebase von unserem Feature auf den Master Branch machen, dann schauen unsere Commits wie folgt aus:

Nichtsdestotrotz müssen wir an dieser Stelle noch einen Merge machen, da passiert aber wiederum nichts anderes, als das der Zeiger von dem alten Commit auf den neuesten Commit bewegt wird, jedoch mit dem Unterschied, dass wir an dieser Stelle keinen zusätzlichen Commit für das Mergen erzeugen:

Jetzt schaut unser Master Branch sauber aus, wir haben einen sauberen Entwicklungsstrang und ein Außenstehender würde meinen, dass wir keinen Feature Branch – sprich keinen weiteren Branch – für die Entwicklung verwendet haben.

Rebase mit Konflikten

Wenn es zu einem Konflikt kommt, so wird das nicht, wie bei einem Merge, in einem extra Commit verpackt, sondern die zwei Commits, die von den Änderungen betroffen sind, werden entweder fusioniert oder einer von den beiden Commits gewinnt. Entscheidungsträger ist hier wiederum der Entwickler, der gerade einen Rebase vornimmt.

 

Was ist ein Git Reset? 

Viele „Git-Neulinge“ verwechseln Reset mit Rebase. Oft glaubt man, dass Rebase irgendetwas mit dem Zurücksetzen der Änderungen zu tun hat, was aber nicht stimmt.

Mit Reset hat man zwei Möglichkeiten um das Zurücksetzen der Daten bzw. den Änderungen bis zu einem, in der Vergangenheit liegenden, bestimmten Commit zu erreichen:

 

Welche Arten von Git Reset gibt es?

Hard Reset

Gehen wir davon aus, dass wir einen Feature Branch eröffnet haben und dem Feature, welches ein Change Request vom Kunden war, eine Absage erteilt wird. Wir wissen schon, dass wir an einem weiteren Feature arbeiten werden, welches aber nichts mit unserem bisherigen Feature Entwicklungen zu tun hat.

In diesem Fall werden wir einen Hard Reset machen. Die bisherigen Entwicklungen am Feature Branch werden verworfen und der Feature Branch wird mit dem ausgewählten Branch, in unserem Fall dem Master Branch, gemerged um wieder auf dem gleichen Datenstand zu sein:

Mixed Reset

Bei einem Mixed Reset schaut das ganze fast gleich aus, mit dem Unterschied, dass wir nicht alles verwerfen, sondern einige Änderungen, die sich in der Staging Area befinden, mitgenommen werden können.

Zusammenfassung

Nachdem wir mit dieser Git Anleitung jetzt einiges über Git wissen und auch schon die wichtigsten Begrifflichkeiten und Funktionalitäten kennengelernt haben, können wir uns im dritten Teil unserer Blogreihe ansehen, wie man Git effizient bei der Entwicklung mit der SAP WebIDE einsetzt.

SAP Java Entwicklung in der Cloud Platform lernen – Teil 2

SAP Java Entwicklung in der Cloud Platform lernen – Teil 2

Java Entwicklung in der SAP Cloud Platform – Teil 2

Lernen Sie mit dem Team der CloudDNA, alles über SAP Java Entwicklung in der SAP Cloud Platform. Wir erklären Ihnen alles was sie darüber wissen müssen.

Zugriff auf SAP HANA in der NEO Umgebung- Java Entwicklung

Im ersten Teil der Blogserie habe ich Sie in die Java Entwicklung auf Basis des Spring Frameworks eingeführt. Anhand eines einfachen RestControllers wurden die Grundlagen gezeigt.

Was wäre eine Applikation ohne Zugriff auf eine Datenbank. Im SAP Cloud Kontext ist es natürlich naheliegend dass SAP HANA als Datenbank verwendet wird. In diesem Teil der Blogserie zeige ich Ihnen wie Sie auf die Datenbank in der NEO Umgebung zugreifen können.

JNDI – Die gelben Seiten- Java Entwicklung

Der Zugriff auf die HANA Datenbank erfolgt über JNDI, aus meiner Sicht eine der spannendsten Technologien im Java Umfeld. Sie werden es kennenlernen und gerne damit arbeiten. Mit einem JNDI Lookup können Sie von der Laufzeitumgebung verwaltete Resourcen laden. Die Idee dahinter ist sehr einfach – die Laufzeitumgebung kümmert sich um das Instanzieren der benötigten Klassen und stellt diese der Applikation bereit.

Damit die hier vorgestellte Applikation JNDI unterstützt müssen Sie einige Tätigkeiten durchführen. Im ersten Schritt ist es erforderlich dass Sie im Verzeichnis src > main eine Unterverzeichnis namens webapp und darin in Unterverzeichnis namens WEB-INF anlegen. Nun müssen Sie im Verzeichnis src > main > webapp > WEB-INF eine Datei namens web.xml anlegen.

Verzeichnisstruktur

Verzeichnisstruktur für web.xml

Jene Resourcen die mittels JNDI verwaltet werden, müssen Sie  in der Datei web.xml als Resource Reference (resource-ref) anführen. Darin sollten Sie den Name (res-ref-name) definieren, unter dem die Resource angesprochen wird. Sowie die dahinterliegende Java Klasse (res-type) auf die typisiert wird.

 

<?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">
   <resource-ref>
     <res-ref-name>jdbc/DefaultDB</res-ref-name>
     <res-type>javax.sql.DataSource</res-type>
   </resource-ref>
</web-app>

Spring Data Dependency deklarieren

Damit haben Sie den ersten Schritt bereits erledigt. Nachdem wir den Zugriff auf die Datenbank nicht mittels SQL Kommandos selbst implementieren, sondern auf Spring Data zurückgreifen, müssen Sie im pom.xml noch eine entsprechende Dependency definieren.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Nachdem Spring Data eine Vielzahl an Datenbanken unterstützt, müssen Sie der Laufzeitumgebung mitteilen welche Datenbank verwendet werden soll. Das passiert über die Datei application.properties. Falls diese noch nicht existiert müssen Sie diese im Verzeichnis src > main > resources anlegen. Der vollqualifizierte Klassenname des HANA Treibers lautet com.sap.db.jdbc.Driver

spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.HANAColumnStoreDialect
spring.jpa.properties.hibernate.connection.pool_size = 10
spring.datasource.driverClassName=com.sap.db.jdbc.Driver

@Configuration- SAP Java Entwicklung

Spring @Configuration-Annotation ist Teil des Spring Core Frameworks. Die Spring-Annotation weist Sie darauf hin, dass die Klasse über eine @Bean-Definitionsmethoden verfügt. Der Spring-Container kann dadurch die Klasse verarbeiten und Spring Beans zur Laufzeit generieren, die Sie in der Anwendung verwenden können.

Für die Verwendung im Neo Stack müssen Sie die Datasource mittels JNDI laden. Dazu sollten Sie eine entsprechende Klasse erstellen. In der nachfolgenden Code-Snippet wird die vollständige Klasse dargestellt.

Neo Datasource Configuration

package at.clouddna.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import javax.sql.DataSource;
import java.sql.SQLException;
@Configuration
@Profile({"neo"})
public class NeoConfig 
{	
	@Bean(destroyMethod="")
	public DataSource jndiDataSource() throws IllegalArgumentException, SQLException
	{
		JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
		DataSource ds = dataSourceLookup.getDataSource("java:comp/env/jdbc/DefaultDB");
		return ds;
	}
}

Der JNDI-Lookup zielt auf den Namen java:comp/env/jdbc/DefaultDB ab. Der Prefix java:comp/env/ ist in der SAP Cloud Platform immer derselbe. Der dahinter definierte Name jdbc/DefaultDB entspricht dem res-ref-name in der web.xml

Entwicklung einer Entity-Klasse

Die Verwendung von Spring-Data ermöglicht Ihnen eine sehr effiziente Entwicklung der Persistenzschicht. Spring-Data basiert auf dem Hibernate Framework. Sobald wir eine Klasse anlegen und diese mit der Annotation @Entity versehen, wird auf der darunterliegenden Datenbank eine zugehörige Tabelle angelegt. Im nachfolgende Code-Snippet zeige ich Ihnen eine einfach User Klasse.

User.java

package at.clouddna.demo.model;
import javax.persistence.*;
@Entity
public class User {
    @Column(nullable = false)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Long id;
    private String firstname;
    private String lastname;
    public Long getId() {
        return this.id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }
    public String getFirstname() {
        return this.firstname;
    }
    public void setLastname(String lastname) {
        this.lastname = lastname;
    }
    public String getLastname() {
        return this.lastname;
    }
}

CRUD Methoden

Das geniale an Spring-Data ist die Out-of-the-box Verfügbarkeit von CRUD Methoden. Dazu müssen sie lediglich ein Interface erstellen, dass vom JpaRespository erbt. Dies wird für die User Entity im nachfolgenden Code-Snippet dargestellt.

package at.clouddna.demo.repository;
import at.clouddna.demo.model.User;
public interface IUserRepository extends JpaRepository<User, Long> {
}

Das Respository können Sie  nun direkt im Controller verwenden, indem über Autowiring darauf zugegriffen wird. Mein Unternehmen sieht davon jedoch ab. Wir erstellen für jede Entity-Klasse immer ein zugehöriges DTO (Data Transfer Object) und erstellen zusätzlich eine Serivce-Klasse, die mit der @Service Annotation versehen wird, welche die Verwendung des Repository kapselt. Die Service-Klasse kann im Controller über die @Autowired Annotation injiziert werden.

Selbstverständlich zeige ich Ihnen wie das funktioniert.

ServiceBase Klasse und Model Mapper

Das Mapping der Entity-Klasse auf das DTO und umgekehrt führen wir nicht manuell sondern mittels ModelMapper durch. Den Modelmapper müssen Sie ins pom.xml als Dependency aufnehmen.

<dependency>
   <groupId>org.modelmapper</groupId>
   <artifactId>modelmapper</artifactId>
   <version>2.3.5</version>
</dependency>

ServiceBase.java

package at.clouddna.codegenerator.service.da;
import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public abstract class ServiceBase {
    private ModelMapper modelMapper;
    public ServiceBase(){
        this.modelMapper = new ModelMapper();
        this.modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STANDARD);
    }
    public <D, T> D map(T entity, Class<D> outClass) {
        return modelMapper.map(entity, outClass);
    }
    public <D, T> List<D> mapAll(Collection<T> entityList, Class<D> outCLass) {
        return entityList.stream()
                .map(entity -> map(entity, outCLass))
                .collect(Collectors.toList());
    }
    protected void writeToFile(String fileName, String content) throws IOException {
        FileOutputStream outputStream = new FileOutputStream(fileName);
        byte[] strToBytes = content.getBytes();
        outputStream.write(strToBytes);
        outputStream.close();
    }
    protected void writeToFile(String fileName, byte[] content) throws IOException {
        FileOutputStream outputStream = new FileOutputStream(fileName);
        outputStream.write(content);
        outputStream.close();
    }
}

Service Klasse

Die Service Klasse erbt von der ServiceBase Klasse und kapselt den Zugriff auf die Datenbank. Nachfolgendes Code-Snippet zeigt die UserService Klasse. Wichtig ist das Sie dabei die Klasse mit der Annotation @Service versehen. Dadurch können sie diese im Controller mittels Autowiring verwenden.

UserService.java

package at.clouddna.demo.service;
import at.clouddna.demo.dto.UserDto;
import at.clouddna.demo.model.User;
import at.clouddna.demo.respository.IUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class UserService extends ServiceBase {
    @Autowired
    private IUserRepository repository;
    public UserDto create(UserDto userDto) {
        return map(repository.save(map(userDto, User.class)), UserDto.class);
    }
    public UserDto update(UserDto userDto) {
        return map(repository.save(map(userDto, User.class)), UserDto.class);
    }
    public boolean delete(Long id) {
        repository.deleteById(id);
        return true;
    }
    public UserDto findById(Long id) {
        Optional<User> userOptional = repository.findById(id);
        if(!userOptional.isPresent()) {
            return null;
        }
        return map(userOptional.get(), UserDto.class);
    }
    public List<UserDto> findAll() {
        return mapAll(repository.findAll(), UserDto.class);
    }
}

RestController

Abschließend zeige ich Ihnen wie der zuvor entwickelte Service im RestController vollumfänglich für alle CRUD-Methoden verwendet werden kann. Sie werden überrascht sein wie einfach das möglich ist!

UserController.java

package at.clouddna.demo.controller;
import at.clouddna.demo.dto.UserDto;
import at.clouddna.demo.model.User;
import at.clouddna.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    @GetMapping
    public ResponseEntity getAll() {
        return ResponseEntity.ok(userService.findAll());
    }
    @GetMapping("/{id}")
    public ResponseEntity getById(@PathVariable("id") Long id) {
        return ResponseEntity.ok(userService.findById(id));
    }
    @PostMapping
    public ResponseEntity create(@RequestBody UserDto body) {
        return ResponseEntity.ok(userService.create(body));
    }
    @PutMapping("/{id}")
    public ResponseEntity update(@PathVariable("id") Long id,
                                 @RequestBody UserDto body) {
        body.setId(id);
        return ResponseEntity.ok(userService.update(body));
    }
    @DeleteMapping("/{id}")
    public ResponseEntity delete(@PathVariable("id") Long id) {
        return ResponseEntity.ok(userService.delete(id));
    }
}

Fazit zur SAP Java Entwicklung in der Cloud Platform

Ich hoffe dass ich Ihnen in diesem Teil die Java Entwicklung in der SAP Cloud Platform schmackhaft gemacht habe. Wie Sie hoffentlich erkannt haben handelt es sich um kein Hexenwerk und keine Weltraumwissenschaft. Einen Tipp möchte ich Ihnen noch mitgeben – achten Sie bereits in der Planung des Projekts darauf dass alles sauber strukturiert ist und Sie jeweils ein eigenes Paket für Entity, DTO, Repository, Service und Controller definieren.

FAQ und Fakten über  SAP Java Entwicklung

 

Was bringt mir die  Verwendung von Spring- Data?

Die Verwendung von Spring-Data ermöglicht Ihnen eine sehr effiziente Entwicklung der Persistenzschicht. Spring-Data basiert auf dem Hibernate Framework. Sobald wir eine Klasse anlegen und diese mit der Annotation @Entity versehen, wird auf der darunterliegenden Datenbank eine zugehörige Tabelle angelegt.

Welche Technologie kann ich im Java Umfeld verwenden ?

Der Zugriff auf die HANA Datenbank erfolgt über JNDI, aus meiner Sicht eine der spannendsten Technologien im Java Umfeld. Mit einem JNDI Lookup können Sie von der Laufzeitumgebung verwaltete Resourcen laden. Die Idee dahinter ist sehr einfach – die Laufzeitumgebung kümmert sich um das Instanzieren der benötigten Klassen und stellt diese der Applikation bereit.

SAP Cloud Platform Java Development

SAP Cloud Platform Java Development

Java develoment for SAP Cloud Platform

In this blog series I will show you how the Java development for the SAP Cloud Plaform is done based on Spring Boot. Spring Boot is the defacto standard framework for developing Java applications in the Cloud / Software-as-a-Service.

There are many resources available on the Internet for development with Spring Boot, but the SAP Cloud Platform specifics mostly fell by the wayside. SAP offers an SDK in the neo stack of the SAP Cloud Platform that enables the use of SAP’s own functionalities in Java. This blog focuses on the general developer tasks in the development of Java applications. The structure of the pom.xml is shown and a simple rest controller is implemented.

I will show the following contents in future blogs:

  • Access HANA DB via JNDI
  • Access Destinations via JNDI
  • Access Tenant Information via JNDI
  • Access User Information

pom.xml Structure

The pom.xml contains all relevant information required to build the application.

Parent

After we set to Spring Boot this must be defined as parent.

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.3.1.RELEASE</version>
  <relativePath />
</parent>

Properties

The Java version and the version of the SAP Cloud SDK are defined in the properties.

<properties>
  <java.version>1.8</java.version>
  <sap.cloud.sdk.version>3.107.18</sap.cloud.sdk.version>
</properties>

Dependencies

In the dependencies the dependencies are defined. Among other things, references are made to SpringBoot Starter Web and also to the SAP Cloud SDK. With the SAP Cloud SDK it is important that the link is to the version defined in the properties and that the scope is set to the value provided. This means that this dependency is not packed in the WAR file and is considered as provided to the server.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId> 
</dependency>
<dependency> 
  <groupId>com.sap.cloud</groupId>
  <artifactId>neo-java-web-api</artifactId>
  <version>${sap.cloud.sdk.version}</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
  <version>2.8.6</version>
</dependency>

Profile

In this example, a separate profile is created for the deployment on the neo stack. This profile defines that certain dependencies are provided by the server and are not embedded in the WAR file. Otherwise the start of the application would fail.

<profile>
  <id>neo</id>
  <activation>
    <activeByDefault>true</activeByDefault>
  </activation>
  <properties>
    <packaging.type>war</packaging.type>
  </properties> 
  <dependencies> 
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <scope>provided</scope>
    </dependency>  
  </dependencies>
</profile>

Build Plugins

Finally, the Spring Boot Maven plugin must be defined in the build.

<plugins>
  <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
  </plugin>
</plugins>

Build the application for the SAP Cloud Platform

The build of the application can be executed via the CommandLine with the following command

mvn clean package -Pneo

In order to successfully launch the application from within Tomcat, the configure method must be redefined from the SpringBootServletInitializer. This can be done either directly in the main class of the application or in a separate class. Personally I prefer to use my own class at this point.

package at.clouddna.demo;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer {
  @Override
  protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    return application.sources(DocmgmtApplication.class);
  }
}

Implementation of a simple rest controller

So that a functional test can be carried out, I always implement a ping method in a separate controller.

package at.clouddna.docmgmt.controller; 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 
import org.springframework.http.ResponseEntity; 
import org.springframework.web.bind.annotation.GetMapping; 
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.RestController; 
@RestController 
@RequestMapping({ "/ping" }) 
public class PingController { 
  private static final Logger logger = LoggerFactory.getLogger(PingController.class); 
  @GetMapping
  public ResponseEntity<?> ping() { 
    logger.debug("ping called"); 
    return ResponseEntity.ok("Hello World"); 
  }
}

This means we are already finished with a simple application. When deploying via the SAP Cloud Platform Cockpit, make sure that the Java Web Tomcat 8 Runtime is selected and that the profile is transferred as a JVM parameter as follows

-Dspring.profiles.active=neo

The ping controller can be tested directly from the browser, since the corresponding method is called via an HTTP GET request.

SAP Java Entwicklung in der Cloud Platform – Teil 1

SAP Java Entwicklung in der Cloud Platform – Teil 1

Java Entwicklung in der SAP Cloud Platform – Teil 1

In dieser Blogreihe zeige ich Ihnen wie die Java Entwicklung für die SAP Cloud Plaform auf Basis von Spring Boot durchgeführt wird. Spring Boot ist das defacto Standard Framework wenn es um die Entwicklung von Java Applikationen in der Cloud / bei Software-as-a-Service geht.

Zur Entwicklung mit Spring Boot gibt es eine Vielzahl an Resourcen im Internet, jedoch blieben die SAP Cloud Platform Spezifika dabei meist auf der Strecke. SAP bietet im Neo Stack der SAP Cloud Platform  ein SDK an, dass Ihnen die Verwendung von SAP-eigenen Funktionalitäten in Java ermöglicht. Dieser Blog fokusiert sich auf die allgemeinen Entwickleraufgaben in der Entwicklung von Java Applikationen. Dabei zeigen wir den Aufbau der pom.xml, ausserdem gehen wir darauf ein wie sein einen einfachen RestController implementierien.

Folgende Inhalte werde ich in zukünftigen Blogs zeigen:

  • Wie greife ich auf HANA DB mittels JNDI ein?
  • Zugriff auf Destinations mittels JNDI
  • Das zugreifen auf Tenant Informationen mittels JNDI
  • Der Zugriff auf User Informationen

Aufbau der pom.xml

Die pom.xml beinhaltet alle relevanten Informationen die für den Build der Applikation erforderlich sind.

Parent-Java Entwicklung

Nachdem wir auf Spring Boot setzen müssen wir dies als parent definieren

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.3.1.RELEASE</version>
  <relativePath />
</parent>

Properties – Java Entwicklung

In den properties können Sie die Java Version sowie die Version des SAP Cloud SDK definieren.

<properties>
  <java.version>1.8</java.version>
  <sap.cloud.sdk.version>3.107.18</sap.cloud.sdk.version>
</properties>

Dependencies- Java Entwicklung

In den dependencies können Sie die Abhängigkeiten definieren. Dabei wird u.a. auf SpringBoot Starter Web verwiesen und auch auf das SAP Cloud SDK. Beim SAP Cloud SDK ist es wichtig, dass Sie auf die in den properties definierte Version verlinken. Ausserdem sollten Sie den scope auf den Wert provided setzen. Damit wird diese Abhängigkeit nicht in das WAR file gepackt und auf als dem Server bereitgestellt betrachtet.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId> 
</dependency>
<dependency> 
  <groupId>com.sap.cloud</groupId>
  <artifactId>neo-java-web-api</artifactId>
  <version>${sap.cloud.sdk.version}</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
  <version>2.8.6</version>
</dependency>

Profile anlegen- Java Entwicklung

In diesem Beispiel der Java Entwicklung werden wir für das Deployment auf den Neo Stack ein eigenes Profil angelegen. Darin definieren sie, dass bestimmte Abhängigkeiten vom Server bereitgestellt werden und diese nicht in das WAR file eingebettet werden. Andernfalls würde der Start der Applikation fehlschlagen.

<profile>
  <id>neo</id>
  <activation>
    <activeByDefault>true</activeByDefault>
  </activation>
  <properties>
    <packaging.type>war</packaging.type>
  </properties> 
  <dependencies> 
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <scope>provided</scope>
    </dependency>  
  </dependencies>
</profile>

Build Plugins

Abschließend müssen Sie im Build noch das Spring Boot Maven Plugin definieren.

<plugins>
  <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
  </plugin>
</plugins>

Build der Applikation für die SAP Cloud Platform

Sie können den Build der Applikation über die CommandLine mit folgenden Befehl durchführen.

mvn clean package -Pneo

Damit Sie die Applikation innerhalb vom Tomcat erfolgreich starten können, müssen Sie die configure Methode aus dem SpringBootServletInitializer redefinieren. Dies kann entweder direkt in der Hauptklasse der Applikation erfolgen oder in einer eigenen Klasse. Ich persönlich bevorzuge an dieser Stelle die Verwendung einer eigenen Klasse.

package at.clouddna.demo;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer {
  @Override
  protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    return application.sources(DocmgmtApplication.class);
  }
}

Implementierung eines einfachen Rest Controllers

Damit ein Funktionstest durchgeführt werden kann, implementiere ich immer eine Ping Methode in einem eigenen Controller.

package at.clouddna.docmgmt.controller; 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 
import org.springframework.http.ResponseEntity; 
import org.springframework.web.bind.annotation.GetMapping; 
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.RestController; 
@RestController 
@RequestMapping({ "/ping" }) 
public class PingController { 
  private static final Logger logger = LoggerFactory.getLogger(PingController.class); 
  @GetMapping
  public ResponseEntity<?> ping() { 
    logger.debug("ping called"); 
    return ResponseEntity.ok("Hello World"); 
  }
}

Damit sind wir mit einer einfachen Applikation bereits fertig. Beim Deployment über das SAP Cloud Platform Cockpit sollten Sie darauf achten, dass die Runtime Java Web Tomcat 8 ausgewählt wird und dass das Profile als JVM Parameter wie folgt übergeben wird.

-Dspring.profiles.active=neo

Den Ping Controller können Sie direkt aus dem Browser heraus testen, da die entsprechende Methode über einen HTTP GET Request aufgerufen wird.

Nach dem Ping gehts weiter zum 2. Teil unseres Blogserie über SAP Java Entwicklung.