Im ersten Teil dieses Beitrags wurde das neue Aktiv-/Inaktiv-Modell für Shortlinks eingeführt und auf architektonischer Ebene verankert. Ausgehend von der fachlichen Motivation wurde gezeigt, warum ein reines Ablaufdatum für moderne Anwendungsfälle nicht ausreicht und weshalb ein expliziter Aktivitätsstatus erforderlich ist.
Darauf aufbauend wurden die technischen Grundlagen gelegt: Das Core-Domain-Modell wurde um das active-Flag erweitert, die DTOs entsprechend angepasst und die Serialisierung so gestaltet, dass Abwärtskompatibilität gewährleistet bleibt. Ergänzend wurden die administrativen REST-Endpunkte erweitert, um den Aktivitätsstatus gezielt setzen, abfragen und filtern zu können. Auch das Redirect-Verhalten wurde präzisiert, sodass deaktivierte und abgelaufene Shortlinks über klar definierte HTTP-Status-Codes unterscheidbar sind.
Damit ist die strukturelle Basis des Aktiv-/Inaktiv-Modells vollständig etabliert.
Im zweiten Teil rückt nun die praktische Nutzung in den Fokus: Wie der Java-Client diese neuen Fähigkeiten abbildet, wie Benutzer den Aktivitätsstatus komfortabel über die Vaadin-Oberfläche steuern können und wie sich daraus konsistente, effiziente Workflows im Alltag ergeben.
Der Quelltext zu diesem Projektstand befindet sich auf GitHub unter folgender URL: https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-10


Erweiterungen des Java-Clients
Nachdem die Server-API um Funktionen zum Aktivieren und Deaktivieren von Shortlinks erweitert worden war, musste auch der Java-Client entsprechend angepasst werden. Schließlich stellt er für viele Benutzer die primäre Schnittstelle dar, um mit dem URL-Shortener programmatisch zu arbeiten – sei es im Rahmen von Desktop-Tools, Automatisierungen, CI/CD-Pipelines oder eingebetteten Systemen.
Kapitel 5 zeigt im Detail, wie der Client um neue Fähigkeiten erweitert wurde, die das Aktiv/Inaktiv-Modell vollständig unterstützen. Dazu gehören:
- das gezielte Umschalten des Aktivitätsstatus,
- das initiale Setzen von Aktivität und Ablaufdatum beim Anlegen eines Shortlinks,
- sowie das Bearbeiten bestehender Mappings inklusive des neuen Aktivitätsfeldes.
Die Erweiterungen basieren auf einem konsistenten Designansatz: einfache Parameter, klarer Fokus der Methoden, strikte Validierung und eine nachvollziehbare Fehlerbehandlung. Für Benutzer entsteht dadurch eine API, die nicht nur vollständig, sondern auch intuitiv verwendbar ist – unabhängig davon, ob einzelne Werte geändert oder komplette Mappings neu aufgebaut werden.
Neue API: toggleActive(shortCode, active)
Damit der Benutzer den Aktivitätsstatus eines Shortlinks nicht nur über die REST-API, sondern auch bequem über den Java-Client steuern kann, wurde die Client-API um eine neue Methode erweitert. Diese Methode bildet die funktionale Entsprechung zum Toggle-Endpoint auf der Serverseite und ermöglicht es, Shortlinks direkt aus Anwendungen, Skripten oder Automatisierungen zu aktivieren oder deaktivieren.
Die neue API-Methode toggleActive(shortCode, active) übernimmt genau diese Aufgabe. Sie stellt sicher, dass alle relevanten Informationen in der richtigen Struktur an den Server übermittelt werden und die Antwort des Servers in eine geeignete Repräsentation überführt wird. Durch die klare Fokussierung auf das Umschalten des Aktivitätsstatus entfällt für den Benutzer die Notwendigkeit, vollständige Update-Objekte aufzubauen oder unnötige Daten zu senden.
Ein weiterer Vorteil dieser Methode liegt in ihrer Einfachheit: Der Benutzer muss lediglich den Shortcode des zu ändernden Links sowie den gewünschten neuen Status angeben. Die interne Logik des Clients übernimmt alles Weitere – vom Erstellen der passenden Request-Payload bis zur Interpretierung der Serverantwort. Dies macht die Verwendung besonders intuitiv und reduziert potenzielle Fehlerquellen.
Im nächsten Schritt betrachten wir die konkrete Implementierung dieser Methode anhand des Originalquellcodes. Die nachfolgende Implementierung stammt direkt aus dem URLShortenerClient:
public boolean toggleActive(String shortCode, boolean active)
throws IOException {
logger().info("Toggle Active shortCode='{}' active='{}'", shortCode, active);
if (shortCode == null || shortCode.isBlank()) {
throw new IllegalArgumentException("shortCode must not be null/blank");
}
final URI uri = serverBaseAdmin.resolve(PATH_ADMIN_TOGGLE_ACTIVE + "/" + shortCode);
final URL url = uri.toURL();
logger().info("Toggle Active - {}", url);
final HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("PUT");
con.setDoOutput(true);
con.setRequestProperty(CONTENT_TYPE, JSON_CONTENT_TYPE);
con.setRequestProperty(ACCEPT, APPLICATION_JSON);
con.setConnectTimeout(CONNECT_TIMEOUT);
con.setReadTimeout(READ_TIMEOUT);
var req = new ToggleActiveRequest(shortCode, active);
final String body = toJson(req);
logger().info("Toggle Active - request body - '{}'", body);
try (OutputStream os = con.getOutputStream()) {
os.write(body.getBytes(UTF_8));
}
final int code = con.getResponseCode();
logger().info("Toggle Active - responseCode {}", code);
if (code == 200 || code == 204 || code == 201) {
drainQuietly(con.getInputStream());
return true;
}
if (code == 404) {
logger().info("shortCode not found.. {}", shortCode);
drainQuietly(con.getErrorStream());
return false;
}
final String err = readAllAsString(con.getErrorStream());
throw new IOException("Unexpected response: " + code + ", body=" + err);
}
Die Methode beginnt mit grundlegender Validierung und dem Logging. Der Parameter shortCode muss zwingend gesetzt sein, da er den zu ändernden Shortlink eindeutig identifiziert. Ist der Wert null oder leer, wirft der Client eine IllegalArgumentException, noch bevor ein HTTP-Aufruf erfolgt.
Im nächsten Schritt wird die Ziel-URL für den API-Aufruf erstellt. Dabei wird der administrative Basis-Pfad des Servers (serverBaseAdmin) mit dem Pfadsegment des Toggle-Endpoints kombiniert. Dank dieser dynamischen Zusammensetzung bleibt der Client in verschiedenen Deployment-Umgebungen flexibel.
Im Anschluss öffnet der Client eine HTTP-Verbindung und konfiguriert sie für eine PUT-Anfrage. Die Methode setzt die erwarteten Headerfelder, darunter Content-Type für JSON sowie Accept, um die Art der erwarteten Antwort zu definieren. setDoOutput(true) signalisiert, dass im Rahmen der Anfrage ein Request-Body übertragen wird.
Für die tatsächliche Payload wird eine Instanz von ToggleActiveRequest erzeugt, die aus shortCode und dem gewünschten neuen Active-Status besteht. Diese Struktur wird mittels toJson serialisiert und anschließend in den Ausgabestream der Verbindung geschrieben.
Nach dem Versand der Anfrage liest die Methode den HTTP-Statuscode über con.getResponseCode() ab. Die Implementierung unterscheidet zwischen drei wesentlichen Fällen:
- Erfolgreiche Statusänderung (200, 204 oder 201): Die Methode leert den InputStream über
drainQuietlyund gibttruezurück. Dies signalisiert dem Benutzer, dass der Shortlink erfolgreich aktualisiert wurde. - Shortlink nicht gefunden (404): Auch hier wird der ErrorStream geleert. Die Methode gibt jedoch
falsezurück, um dem Benutzer deutlich zu machen, dass der Shortlink nicht existiert und daher nicht aktualisiert werden konnte. - Alle sonstigen Fehlerfälle: Bei unerwarteten oder fehlerhaften Antworten wird der ErrorStream ausgelesen und gemeinsam mit dem HTTP-Statuscode in einer
IOExceptionverpackt. Dies zwingt den aufrufenden Code dazu, sich aktiv mit unvorhergesehenen Fehlern auseinanderzusetzen und verhindert, dass solche Zustände stillschweigend übergangen werden.
Damit bietet toggleActive eine klar definierte und robuste API zum Umschalten des Aktivitätsstatus über den Java-Client. Sie folgt denselben Designprinzipien wie die anderen Methoden des Clients: klare Validierung, konsistente Fehlerbehandlung, aussagekräftiges Logging und eine schlanke, JSON-basierte Kommunikation. Die Implementierung fügt sich damit homogen in die vorhandene Client-Architektur ein und bietet eine einfache, aber wirkungsvolle Erweiterung des Funktionsumfangs.
Erweitertes createCustomMapping(...)
Neben der Möglichkeit, bestehende Shortlinks nachträglich zu aktivieren oder zu deaktivieren, wurde auch der Prozess zur Erstellung neuer Shortlinks erweitert. Damit Benutzer bereits beim Anlegen festlegen können, ob ein Shortlink aktiv sein soll oder nicht, wurde die Methode createCustomMapping(...) im Java-Client funktional erweitert.
Vor dieser Anpassung konnte ein Shortlink ausschließlich mit seinen grundlegenden Eigenschaften erzeugt werden – Shortcode, Original-URL und optionales Ablaufdatum. Der Aktivitätsstatus wurde implizit gesetzt oder war ausschließlich über spätere Bearbeitungsschritte steuerbar. Mit der neuen Erweiterung erhält der Benutzer die Möglichkeit, direkt im Erstellungsprozess festzulegen, ob ein Shortlink initial aktiv oder deaktiviert angelegt wird.
Die Methode folgt denselben Prinzipien wie die anderen Funktionen des Clients: Sie legt den Fokus auf eine klare, einfache und zuverlässige Kommunikation mit dem Server. Der Benutzer stellt lediglich die benötigten Eingabewerte bereit, während der Client die gesamte technische Abwicklung der Anfrage übernimmt – vom Erstellen des Request-Objekts über die JSON-Serialisierung bis hin zur Interpretation der Serverantwort.
Durch diese Erweiterung lassen sich neue Anwendungsfälle realisieren. Beispielsweise kann ein Shortlink bereits angelegt, aber erst später aktiviert werden – etwa synchron zu einem Release-Zeitpunkt, einer Marketingkampagne oder automatisiert in CI/CD-Pipelines. Gleichzeitig gewährleistet die einheitliche Datenstruktur, dass der Aktivitätsstatus eines Shortlinks sowohl beim Anlegen als auch beim Bearbeiten konsistent behandelt wird.
Im nächsten Schritt betrachten wir die konkrete Implementierung dieser Methode anhand der relevanten Quelltextabschnitte und analysieren, wie die erweiterten Parameter in den Erstellungsprozess eingebunden werden.
Die Erweiterung zeigt sich im URLShortenerClient in Form zweier überladener Methoden: einer einfachen Variante und einer erweiterten Version mit Ablauf- und Aktivitätsparametern:
public ShortUrlMapping createCustomMapping(String alias, String url)
throws IOException {
logger().info("Create custom mapping alias='{}' url='{}'", alias, url);
return createCustomMapping(alias, url, null, null);
}
public ShortUrlMapping createCustomMapping(String alias, String url, Instant expiredAtOrNull, Boolean activeOrNull)
throws IOException {
logger().info("Create custom mapping alias='{}' url='{}' expiredAt='{}' active='{}'", alias, url, expiredAtOrNull, activeOrNull);
var result = UrlValidator.validate(url);
if (!result.valid()) {
throw new IllegalArgumentException("Invalid URL: " + result.message());
}
if (alias != null && !alias.isBlank()) {
var validate = AliasPolicy.validate(alias);
if (!validate.valid()) {
var reason = validate.reason();
throw new IllegalArgumentException(reason.defaultMessage);
}
}
var shortenRequest = new ShortenRequest(url, alias, expiredAtOrNull, activeOrNull);
String body = toJson(shortenRequest);
logger().info("createCustomMapping - body - '{}'", body);
URL shortenUrl = serverBaseAdmin.resolve(PATH_ADMIN_SHORTEN).toURL();
logger().info("connecting to .. shortenUrl {} (custom)", shortenUrl);
HttpURLConnection connection = (HttpURLConnection) shortenUrl.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setRequestProperty(CONTENT_TYPE, JSON_CONTENT_TYPE);
try (OutputStream os = connection.getOutputStream()) {
os.write(body.getBytes(UTF_8));
}
int status = connection.getResponseCode();
logger().info("Response Code from Server - {}", status);
if (status == 200 || status == 201) {
try (InputStream is = connection.getInputStream()) {
String jsonResponse = new String(is.readAllBytes(), UTF_8);
logger().info("createCustomMapping - jsonResponse - {}", jsonResponse);
ShortUrlMapping shortUrlMapping = fromJson(jsonResponse, ShortUrlMapping.class);
logger().info("shortUrlMapping .. {}", shortUrlMapping);
return shortUrlMapping;
}
}
if (status == 409) {
final String err = readAllAsString(connection.getErrorStream());
throw new IllegalArgumentException("Alias already in use: '" + alias + "'. " + err);
}
if (status == 400) {
final String err = readAllAsString(connection.getErrorStream());
throw new IllegalArgumentException("Bad request: " + err);
}
throw new IOException("Server returned status " + status);
}
Die einfache Variante von createCustomMapping dient als Komfortmethode: Sie nimmt nur Alias und URL entgegen und delegiert mit Nullwerten für Ablaufdatum und Aktivitätsstatus an die erweiterte Version. Dadurch bleibt die API für einfache Anwendungsfälle schlank, während die vollständige Kontrolle über den Shortlink über die überladene Methode möglich ist.
Die erweiterte Methode übernimmt zunächst die Validierung der Eingabedaten. Mit UrlValidator.validate(url) wird geprüft, ob die angegebene Ziel-URL die erwarteten Kriterien erfüllt. Ist dies nicht der Fall, wird eine IllegalArgumentException mit einer nachvollziehbaren Fehlermeldung ausgelöst. Anschließend wird – sofern ein Alias gesetzt ist – die Alias-Policy geprüft. Damit wird sichergestellt, dass benutzerdefinierte Shortcodes den festgelegten Regeln entsprechen und z.B. keine unerwünschten Sonderzeichen oder verbotenen Muster enthalten.
Sind URL und Alias gültig, wird ein ShortenRequest aufgebaut, der neben URL und Alias auch expiredAtOrNull und activeOrNull enthält. Auf diese Weise kann der Benutzer bereits beim Anlegen steuern, ob der Shortlink ein Ablaufdatum hat und ob er initial aktiv oder inaktiv sein soll. Der Request wird anschließend in JSON-Format serialisiert und an das PATH_ADMIN_SHORTEN-Endpoint gesendet.
Die HTTP-Konfiguration folgt dem bekannten Muster: Es wird ein POST-Request mit JSON-Body erstellt, das Content-Type entsprechend gesetzt wird und der Body über den OutputStream übertragen wird. Die Antwort des Servers wird zunächst anhand des HTTP-Statuscodes geprüft. Bei Erfolg (200 oder 201) liest der Client den Response-Body, wandelt ihn in ein ShortUrlMapping-Objekt um und gibt es an den Benutzer zurück.
Für Fehlerfälle sind zwei spezielle Pfade vorgesehen: Schlägt die Alias-Reservierung fehl, weil der Alias bereits vergeben ist (409 Conflict), wird eine IllegalArgumentException mit einer klaren Beschreibung geworfen. Bei allgemeinen Validierungsfehlern (400 Bad Request) wird ebenfalls eine aussagekräftige IllegalArgumentException ausgelöst. Alle anderen unerwarteten Statuscodes führen zu einer IOException, die die genaue Status- und Fehlermeldung des Servers enthält.
Insgesamt fügt sich die erweiterte createCustomMapping-Methode nahtlos in das bestehende API-Design ein. Sie ermöglicht es Benutzern, Shortlinks in einem Schritt mit Alias, Ablaufdatum und Aktivitätsstatus anzulegen und verbindet dabei eine strikte Validierung mit einer klaren, vorhersehbaren Fehlerbehandlung.
Anpassungen an edit(...) – Bearbeitung mit Aktivitätsstatus
Neben dem Erstellen neuer Shortlinks müssen Benutzer häufig auch bestehende Einträge anpassen. Das kann notwendige Aktualisierungen der Ziel-URL betreffen, das Anpassen eines Ablaufdatums – oder im Kontext der neuen Funktionalität auch das gezielte Aktivieren oder Deaktivieren eines bestehenden Shortlinks.
Um diese Anforderungen abzudecken, wurde die bestehende edit(...) - Methode des Java-Clients erweitert. Sie unterstützt nun ebenfalls den optionalen Parameter für den Aktivitätsstatus, sodass der Benutzer diesen Wert nicht über eine nachgelagerte Toggle-API ändern muss, sondern ihn direkt im Editiervorgang steuern kann.
Dieser Ansatz erleichtert Workflows, in denen mehrere Eigenschaften eines Shortlinks gleichzeitig bearbeitet werden sollen. Anstatt mehrere API-Aufrufe nacheinander absetzen zu müssen, kann der gesamte Änderungsprozess in einer einzigen Bearbeitungsoperation zusammengefasst werden. Die API bleibt dadurch effizient, konsistent und gut in verschiedene Anwendungsszenarien integrierbar – vom manuellen Bearbeiten über die UI bis hin zu automatisierten Abläufen in Skripten oder Backend-Systemen.
Im nächsten Schritt werden wir die konkrete Implementierung der erweiterten edit(...)-Methode betrachten.
Die folgende Implementierung stammt aus dem URLShortenerClient und zeigt, wie der Aktivitätsstatus in den Bearbeitungsprozess eingebunden wurde:
public boolean edit(String shortCode, String newUrl, Instant expiresAtOrNull, Boolean activeOrNull)
throws IOException {
logger().info("Edit mapping alias='{}' url='{}' expiredAt='{}' active='{}'", shortCode, newUrl, expiresAtOrNull, activeOrNull);
if (shortCode == null || shortCode.isBlank()) {
throw new IllegalArgumentException("shortCode must not be null/blank");
}
if (newUrl == null || newUrl.isBlank()) {
throw new IllegalArgumentException("newUrl must not be null/blank");
}
final URI uri = serverBaseAdmin.resolve(PATH_ADMIN_EDIT + "/" + shortCode);
final URL url = uri.toURL();
logger().info("edit - {}", url);
final HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("PUT");
con.setDoOutput(true);
con.setRequestProperty(CONTENT_TYPE, JSON_CONTENT_TYPE);
con.setRequestProperty(ACCEPT, APPLICATION_JSON);
con.setConnectTimeout(CONNECT_TIMEOUT);
con.setReadTimeout(READ_TIMEOUT);
final ShortenRequest req = new ShortenRequest(newUrl, shortCode, expiresAtOrNull, activeOrNull);
final String body = toJson(req);
logger().info("edit - request body - '{}'", body);
try (OutputStream os = con.getOutputStream()) {
os.write(body.getBytes(UTF_8));
}
final int code = con.getResponseCode();
logger().info("edit - responseCode {}", code);
if (code == 200 || code == 204 || code == 201) {
drainQuietly(con.getInputStream());
return true;
}
if (code == 404) {
logger().info("shortCode not found.. {}", shortCode);
drainQuietly(con.getErrorStream());
return false;
}
final String err = readAllAsString(con.getErrorStream());
throw new IOException("Unexpected response: " + code + ", body=" + err);
}
Die Methode übernimmt vier Parameter: den zu ändernden Shortcode, die neue Ziel-URL, ein optionales Ablaufdatum sowie den optionalen Aktivitätsstatus. Bereits zu Beginn wird geprüft, ob die notwendigen Felder – insbesondere shortCode und newUrl – gültig sind. Ungültige Eingaben werden unmittelbar durch eine IllegalArgumentException abgefangen.
Der Request wird anschließend als PUT an das entsprechende Edit-Endpoint gesendet. Die Payload besteht aus einem ShortenRequest, der alle editierbaren Attribute eines Shortlinks enthält – inklusive der Aktivitätsangabe activeOrNull. Damit kann der Benutzer im selben Vorgang sowohl die URL als auch das Ablaufdatum und den Aktivitätsstatus ändern.
Der Client verarbeitet anschließend die HTTP-Antwort. Erfolgsfälle (200, 201, 204) führen zu einem Rückgabewert von true, während ein 404 eindeutig signalisiert, dass der Shortlink nicht existiert. Unerwartete Statuscodes führen zu einer IOException, die dem Benutzer klare Fehlerdiagnosen ermöglicht.
Durch diese Erweiterung ist die edit(...)-Methode nun vollständig in der Lage, alle relevanten Eigenschaften eines Shortlinks in einem einzigen Schritt zu aktualisieren – einschließlich des Aktivitätsstatus, der zuvor ausschließlich über ein separates Toggle-Endpoint steuerbar war.
Benutzerinteraktionen und UI-Logik in der Vaadin-Oberfläche
Nachdem in den vorherigen Kapiteln die serverseitigen REST-Handler, die Client-APIs sowie die Persistenz betrachtet wurden, fokussiert sich dieses Kapitel auf die UI-Schicht der Anwendung. Die OverviewView ist das zentrale Administrationswerkzeug für den Benutzer, um Shortlinks zu durchsuchen, zu filtern, zu bearbeiten und in größeren Mengen zu verwalten.
Kapitel 6 beleuchtet daher die wichtigsten Benutzerinteraktionen im Frontend, insbesondere:
- das Zusammenspiel von Vaadin-Grid, CallbackDataProvider und dynamischen Filterparametern
- die Integration von Single-Actions (z. B. Aktivieren/Deaktivieren eines Links per Icon-Klick)
- die Implementierung komplexerer Bulk-Operationen inklusive Dialoge, Fehlerbehandlung und visueller Rückmeldung
- die Verbindung zwischen UI-Eingaben und den REST-Endpunkten über den
URLShortenerClient
Umschalten des Aktivzustands direkt im Grid
Damit Benutzer den Aktiv- oder Inaktivstatus eines Shortlinks nicht nur über die API, sondern auch bequem in der Benutzeroberfläche ändern können, wurde das Grid der Verwaltungsansicht erweitert. Das Ziel besteht darin, eine möglichst direkte, intuitive und risikoarme Möglichkeit zu schaffen, den Status eines Shortlinks umzuschalten, ohne dafür separate Dialoge oder Editiermasken öffnen zu müssen.
Im Zentrum jeder Tabellenzeile steht eine klar erkennbare visuelle Schaltfläche. Mit einem einzigen Klick kann der Benutzer den Status eines Shortlinks umschalten – von aktiv zu inaktiv oder umgekehrt. Die Oberfläche signalisiert dabei unmittelbar, welchem Zustand der Shortlink aktuell zugeordnet ist und welche Aktion beim Klick erfolgen wird.
Diese Erweiterung verfolgt vor allem drei Ziele:
- Schnelligkeit – häufiges Umschalten erfordert keine Navigation in weitere Masken.
- Transparenz – der Benutzer sieht jederzeit, welche Shortlinks aktuell aktiv sind.
- Robustheit – mögliche Fehlersituationen werden klar kommuniziert und beeinträchtigen die restliche Anwendung nicht.
Der Kern der Umsetzung liegt in der Konfiguration des Grids und der neuen “Active”-Spalte. Der relevante Ausschnitt aus der OverviewView sieht wie folgt aus:
grid.addComponentColumn(m -> {
Icon icon = m.active()
? VaadinIcon.CHECK_CIRCLE.create()
: VaadinIcon.CLOSE_CIRCLE.create();
icon.setColor(m.active()
? "var(--lumo-success-color)"
: "var(--lumo-error-color)");
icon.getStyle().set("cursor", "pointer");
icon.getElement().setProperty("title",
m.active() ? "Deactivate" : "Activate");
icon.addClickListener(_ -> {
boolean newValue = !m.active();
try {
urlShortenerClient.toggleActive(m.shortCode(), newValue);
Notification.show("Status updated", 2000, Notification.Position.TOP_CENTER);
safeRefresh();
} catch (Exception ex) {
Notification.show("Error updating active status: " + ex.getMessage(),
3000, Notification.Position.TOP_CENTER);
}
});
return icon;
})
.setHeader("Active")
.setKey("active")
.setAutoWidth(true)
.setResizable(true)
.setSortable(true)
.setFlexGrow(0);
Statt eines reinen Textfelds wird hier eine Component-Spalte verwendet, die für jede Zeile ein eigenes Icon darstellt. Die Wahl des Icons hängt direkt vom aktuellen Aktivitätsstatus des jeweiligen Shortlinks ab:
- Ist
m.active()true, wird einCHECK_CIRCLE-Icon angezeigt. - Ist
m.active()false, wird einCLOSE_CIRCLE-Icon verwendet.
Über die Farbgebung (success für aktiv, error für inaktiv) kann der Benutzer den Zustand auf einen Blick erkennen. Zusätzlich wird über das title-Attribut am Icon mitgeteilt, welche Aktion durch einen Klick ausgelöst wird (“Activate” bzw. “Deactivate”).
Der eigentliche Toggle-Mechanismus ist im Click-Listener des Icons implementiert. Bei einem Klick wird zunächst der gewünschte neue Status berechnet:
boolean newValue = !m.active();
Anschließend wird der Java-Client aufgerufen, der den Statuswechsel über die REST-API an den Server delegiert:
urlShortenerClient.toggleActive(m.shortCode(), newValue);
Führt der Aufruf zum Erfolg, erhält der Benutzer eine kurze, unaufdringliche Bestätigung per Notification und das Grid wird über safeRefresh() neu geladen. Dadurch werden auch nachfolgende Änderungen (z. B. Filterung nach Aktivstatus) sofort korrekt dargestellt.
Die Fehlerbehandlung folgt hier dem gleichen Muster wie in der Client-API: Tritt eine Exception auf – sei es durch Netzwerkprobleme, unerwartete HTTP-Statuscodes oder Serverfehler –, wird diese im UI über eine gut sichtbare Notification kommuniziert:
} catch (Exception ex) {
Notification.show("Error updating active status: " + ex.getMessage(),
3000, Notification.Position.TOP_CENTER);
}
Für den Benutzer bedeutet das: Der Aktivstatus eines Shortlinks lässt sich direkt in der Übersicht per Klick ändern. Die UI kombiniert dabei klare visuelle Hinweise (Icon, Farbe, Tooltip) mit unmittelbarem Feedback und einer robusten Fehlerbehandlung. Das reduziert die Notwendigkeit separater Bearbeitungsdialoge und macht typische Verwaltungsaufgaben rund um Aktiv/inaktiv deutlich effizienter.
Filtern nach Aktiv- und Inaktivstatus
Neben der Möglichkeit, den Aktivstatus eines Shortlinks direkt im Grid umzuschalten, wurde die Benutzeroberfläche um ein präzises Filtersystem erweitert. Dies ermöglicht dem Benutzer, gezielt nach aktiven, inaktiven oder allen Shortlinks zu suchen – ohne manuelle Suche oder komplexe Abfragen.
Das Ziel besteht darin, dem Benutzer ein Werkzeug bereitzustellen, mit dem er die Sichtbarkeit von Einträgen flexibel steuern kann. Dies verbessert sowohl die Übersichtlichkeit großer Datenmengen als auch die Effizienz typischer Verwaltungsaufgaben, etwa beim Erkennen abgelaufener oder deaktivierter Shortlinks.
Der neue Filter wird über ein einheitliches Auswahlelement gesteuert und beeinflusst sowohl die Datenabfrage als auch das Paging. Setzt der Benutzer den Filter auf “Active”, “Inactive” oder “Not set”, wirkt dies direkt auf die Abfrageparameter aus, sodass nur relevante Datensätze geladen und im Grid angezeigt werden.
Zentral ist dabei das Select-Feld für den Aktivstatus, das in der OverviewView definiert ist:
private final Select<ActiveState> activeState = new Select<>();
Dieses UI-Element wird in der Suchleiste konfiguriert und zusammen mit den anderen Such- und Paging-Elementen angezeigt. Die Initialisierung erfolgt im buildSearchBar()-Block:
activeState.setLabel("Active state");
activeState.setItems(ActiveState.values());
activeState.setItemLabelGenerator(state -> switch (state) {
case ACTIVE -> "Active";
case INACTIVE -> "Inactive";
case NOT_SET -> "Not set";
});
activeState.setEmptySelectionAllowed(false);
activeState.setValue(ActiveState.NOT_SET);
HorizontalLayout topBar = new HorizontalLayout(globalSearch, searchScope, pageSize, activeState, resetBtn);
Damit erhält der Benutzer eine klar beschriftete Dropdown-Auswahl mit drei Zuständen:
- Active – nur aktive Shortlinks
- Inactive – nur inaktive Shortlinks
- Not set – keine Filterung nach Aktivstatus
Der Filter ist standardmäßig auf NOT_SET gesetzt, sodass zunächst alle Shortlinks angezeigt werden. Über die Funktion ItemLabelGenerator wird definiert, welche Beschriftung im Dropdown für den jeweiligen Enum-Wert angezeigt wird.
Damit der Filter auch funktional wirksam wird, reagiert die View auf Änderungen am Auswahlfeld. Im addListeners()-Block ist ein entsprechender Listener registriert:
activeState.addValueChangeListener(_ -> {
currentPage = 1;
safeRefresh();
});
Sobald der Benutzer den Aktivstatus ändert, wird die aktuelle Seite auf 1 zurückgesetzt und der Datenprovider neu geladen. Dadurch werden die Filteränderungen direkt sichtbar und es wird verhindert, dass der Benutzer sich auf einer ungültigen Seite befindet (z. B. wenn durch Filtern weniger Einträge vorhanden sind).
Die eigentliche Verbindung zur REST-API entsteht im buildFilter(...)-Methodenblock, der aus der UI einen UrlMappingListRequest erzeugt:
private UrlMappingListRequest buildFilter(Integer page, Integer size) {
UrlMappingListRequest.Builder b = UrlMappingListRequest.builder();
ActiveState activeStateValue = activeState.getValue();
logger().info("buildFilter - activeState == {}", activeStateValue);
if (activeStateValue != null && activeStateValue.isSet()) {
b.active(activeStateValue.toBoolean());
}
// ... weitere Filter (codePart, urlPart, Zeiträume, Sortierung, Paging)
if (page != null && size != null) {
b.page(page).size(size);
}
var filter = b.build();
logger().info("buildFilter - {}", filter);
return filter;
}
Hier wird der Enum-Wert des activeState-Selects ausgelesen und – sofern er als “gesetzt” gilt – in ein Boolean-Boolean (true für aktiv, false für inaktiv) umgewandelt. Dieser Wert wird vom Request-Builder übernommen und später vom Client (URLShortenerClient) als Query-Parameter an den Server übermittelt. Ist der Zustand NOT_SET, wird kein aktiver Wert gesetzt, sodass auf der Serverseite keine Einschränkung nach Aktivstatus erfolgt.
Zusammen bilden diese Bausteine ein konsistentes Filterkonzept:
- Das
Select<ActiveState>bietet dem Benutzer eine verständliche, dreistufige Auswahl. - Der ValueChangeListener sorgt dafür, dass Filteränderungen sofort wirksam werden.
buildFilter(...)übersetzt die UI-Auswahl in einen typisierten Request an die Backend-API.
Für Benutzer entsteht dadurch eine nahtlose Interaktion: Ein Wechsel von “Active” auf “Inactive” führt unmittelbar dazu, dass nur noch die entsprechenden Shortlinks im Grid erscheinen – ohne dass diese Logik im Frontend dupliziert oder clientseitig nachgefiltert werden muss.
Massenoperationen auf Basis des Aktivstatus
Neben der Einzelbearbeitung bietet die Benutzeroberfläche auch komfortable Massenoperationen, mit denen Benutzer mehrere Shortlinks gleichzeitig aktivieren oder deaktivieren können. Dies ist besonders hilfreich in Szenarien, in denen größere Datenbestände verwaltet werden müssen – etwa beim vorübergehenden Abschalten von Kampagnenlinks oder beim Reaktivieren einer ganzen Gruppe zuvor deaktivierter Shortlinks.
In dieser Erweiterung wurde das Bulk-Handling so gestaltet, dass es sich nahtlos in das bestehende Grid sowie in die integrierten Auswahlmechanismen einfügt. Der Benutzer kann beliebig viele Zeilen im Grid markieren und anschließend über klar erkennbare Schaltflächen in der Bulk-Leiste am unteren Bildschirmrand entsprechende Aktionen auslösen.
Der Ablauf folgt dabei immer demselben Muster:
- Der Benutzer markiert die gewünschten Shortlinks.
- Ein Bestätigungsdialog stellt sicher, dass die Massenänderung bewusst ausgelöst wird.
- Für jeden ausgewählten Eintrag wird die Aktion ausgeführt – unabhängig von möglichen Fehlern.
- Das Ergebnis wird dem Benutzer in einer kompakten Erfolgs-/Fehlerübersicht angezeigt.
Durch dieses Vorgehen wird eine robuste und dennoch benutzerfreundliche Massenbearbeitung ermöglicht. Die Oberfläche bleibt responsiv, und die Fehlerbehandlung stellt sicher, dass ein Fehlschlag bei einem einzelnen Shortlink nicht die gesamte Operation beeinträchtigt.
Der folgende Ausschnitt stammt direkt aus der OverviewView und zeigt die zentrale Methode, die eine Menge von Shortlinks in einem Durchlauf aktiviert oder deaktiviert:
private void bulkSetActive(Set<ShortUrlMapping> selected, boolean activate) {
int success = 0;
int failed = 0;
for (var m : selected) {
try {
var ok = urlShortenerClient.toggleActive(m.shortCode(), activate);
if (ok) {
success++;
} else {
failed++;
}
} catch (IOException ex) {
logger().error("Toggle active state failed for {}", m.shortCode(), ex);
failed++;
}
}
grid.deselectAll();
safeRefresh();
var actionLabel = activate ? "Activate" : "Deactivate";
Notification.show(
actionLabel + " – Success: " + success + " • Failed: " + failed
);
}
Diese Methode bildet das Herzstück der Massenaktivierung. Sie erhält die aktuell im Grid ausgewählten Shortlinks sowie den Zielstatus (activate = true oder false). Für jeden Eintrag wird der entsprechende Request über den Java-Client an die Server-API gesendet. Der Ansatz ist bewusst fehlertolerant gestaltet: Ein Fehlschlag bei einem Eintrag hat keine Auswirkungen auf die restliche Verarbeitung.
Die UI wird erst nach Abschluss der Schleife aktualisiert. Dadurch bleibt die Operation übersichtlich und performant, auch bei sehr vielen Shortlinks. Das Ergebnis wird dem Benutzer kompakt zusammengefasst angezeigt.
Der Benutzer löst diese Operation über einen Bestätigungsdialog aus. Dieser ist in confirmBulkSetActiveSelected(boolean activate) implementiert:
private void confirmBulkSetActiveSelected(boolean activate) {
var selected = grid.getSelectedItems();
if (selected.isEmpty()) {
Notification.show("No entries selected");
return;
}
var verb = activate ? "activate" : "deactivate";
var verbCap = activate ? "Activate" : "Deactivate";
Dialog dialog = new Dialog();
dialog.setHeaderTitle(verbCap + " all " + selected.size() + " short links?");
dialog.add(new Text(
"This will " + verb + " all selected short links. "
+ "They will be " + (activate ? "active" : "inactive") + " afterwards."
));
Button cancel = new Button("Cancel", _ -> dialog.close());
Button confirm = new Button(verbCap + " All", _ -> {
dialog.close();
bulkSetActive(Set.copyOf(selected), activate);
});
confirm.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
dialog.getFooter().add(new HorizontalLayout(cancel, confirm));
dialog.open();
}
Dieser Dialog sorgt dafür, dass Massenänderungen nicht versehentlich ausgelöst werden. Besonders bei deaktivierenden Aktionen ist dies relevant, da ein deaktivierter Shortlink nicht mehr weiterleitet.
Aktiviert wird der Dialog über die beiden Buttons in der Bulk-Leiste:
bulkActivateBtn.addClickListener(_ -> confirmBulkActivateSelected());
bulkDiActivateBtn.addClickListener(_ -> confirmBulkDeactivateSelected());
Die zugehörigen Wrapper-Methoden lauten:
private void confirmBulkActivateSelected() {
confirmBulkSetActiveSelected(true);
}
private void confirmBulkDeactivateSelected() {
confirmBulkSetActiveSelected(false);
}
Damit ist die Massenbearbeitung vollständig in die Benutzeroberfläche integriert: Auswahl, Bestätigung, Ausführung und Ergebnisrückmeldung folgen einem klaren, konsistenten Ablaufmodell. Für Benutzer entsteht ein intuitiver Workflow, der administrative Aufgaben erheblich erleichtert.
Redirect-Verhalten für Endnutzer
Mit der Einführung des neuen Aktiv/Inaktiv-Mechanismus und der verbesserten Behandlung von Ablaufzeiten (expiresAt) verändert sich das Verhalten des URL-Shorteners beim Aufruf eines Shortlinks grundlegend. Während Nutzer bisher ausschließlich eine Weiterleitung oder einen generischen Fehler erhielten, liefert das System nun differenzierte HTTP-Statuscodes, die präzise Rückschlüsse auf den Zustand eines Links ermöglichen.
Abgelaufene Shortlinks (expiresAt) → 410 Gone
Wenn ein Shortlink ein Ablaufdatum hat und dieses überschritten ist, darf der Benutzer beim Aufruf nicht mehr zur ursprünglichen URL weitergeleitet werden. Stattdessen muss das System klar signalisieren, dass dieser Shortlink zwar existiert, aber nicht mehr gültig ist. Genau hierfür wird der HTTP-Statuscode 410 Gone verwendet.
Der Statuscode 410 steht für „dauerhaft entfernt“ und macht unmissverständlich deutlich, dass der Shortlink früher aktiv war, aber nun bewusst nicht mehr zur Verfügung steht. Im Gegensatz zu 404 Not Found, das lediglich aussagt, dass etwas nicht existiert, vermittelt 410 eine klare semantische Bedeutung: Dieser Shortlink ist abgelaufen und wird nie wieder gültig sein.
Für den Benutzer bedeutet das: Der Aufruf führt zu einem klaren Fehlerverhalten, das dennoch nachvollziehbar bleibt. Für Entwickler hingegen schafft dieser Mechanismus Transparenz, da sie sauber zwischen drei Situationen unterscheiden können:
- Ein Shortlink existiert nicht → 404 Not Found
- Ein Shortlink existiert, ist aber deaktiviert → 404 Not Found
- Ein Shortlink existiert und ist abgelaufen → 410 Gone
Die konsequente Verwendung des Statuscodes 410 öffnet darüber hinaus die Tür zu besseren Monitoring und saubereren Automatisierungsprozessen. Systeme wie API-Gateways, SEO-Tools, Crawler oder CI/CD-Pipelines können auf das exakte Verhalten reagieren und dadurch genauer klassifizieren, warum eine Weiterleitung nicht erfolgt.
Deaktiviert (active = false) → 404 Not Found
Ein weiterer zentraler Bestandteil des neuen Aktiv/Inaktiv-Modells ist der Umgang mit deaktivierten Shortlinks. Während abgelaufene Links mit dem HTTP-Status 410 Gone signalisiert werden, nutzt das System für deaktivierte Links bewusst einen anderen Status: 404 Not Found.
Dieser Unterschied ist nicht zufällig, sondern folgt klaren sicherheitstechnischen und operativen Überlegungen. Ein deaktivierter Shortlink soll sich für den Endnutzer so verhalten, als existiere er nicht mehr – obwohl er intern weiterhin vollständig vorhanden, sichtbar und verwaltbar bleibt.
Damit entsteht ein Verhalten, das sich ideal für Wartungsphasen, temporäre Blockaden, Kampagnenstopps oder sicherheitsrelevante Abschaltungen eignet. Entwickler und Administratoren behalten die volle Kontrolle über den Datensatz, ohne versehentlich Informationen über interne Zustände preiszugeben.
Cheers Sven