home.social

#advent-2025 — Public Fediverse posts

Live and recent posts from across the Fediverse tagged #advent-2025, aggregated by home.social.

fetched live
  1. E infine, a conclusione di questo 2025, ho creato una specie di "guida" per chiunque volesse cimentarsi nella costruzione di un calendario dell'avvento videoludico.

    Sono solo consigli, in base all'esperienza come creatore ma soprattutto come spettatore di calendari videoludici, di cosa ho gradito e cosa avrei voluto fosse fatto meglio.

    Grazie per tutto il vostro supporto, riprenderò a pubblicare video nel nuovo anno. Felice 2026 a tuttə!

    odysee.com/@GiardinoVideoludic

    #advent2025

    2/2

  2. Illuminatə, namaskar!

    Durante la preparazione dell'avvento, ho registrato una puntata che poi ho deciso di rimuovere.. Ma siccome non sono uno sprecone, è uscita ieri come puntata extra.

    Vi parlo della conversione per DOS di Castlevania, purtroppo a mio parere non riuscita a dovere, con colori davvero improponibili e musiche non all'altezza. Ma mica possiamo sempre parlare di giochi belli, no?

    Se volete, la puntata è disponibile sul canale!

    odysee.com/@GiardinoVideoludic

    #advent2025

    1/2

  3. Illuminatə, namaskar!

    Durante la preparazione dell'avvento, ho registrato una puntata che poi ho deciso di rimuovere.. Ma siccome non sono uno sprecone, è uscita ieri come puntata extra.

    Vi parlo della conversione per DOS di Castlevania, purtroppo a mio parere non riuscita a dovere, con colori davvero improponibili e musiche non all'altezza. Ma mica possiamo sempre parlare di giochi belli, no?

    Se volete, la puntata è disponibile sul canale!

    odysee.com/@GiardinoVideoludic

    #advent2025

    1/2

  4. Christmas Isn’t Safe—It’s Good

    A Christmas Eve Reflection | #ADVENT2025 “Christmas isn’t safe. It’s good.”— S. D. Smith Tonight, we stand at the edge of a manger. We sing of peace on earth and silent nights, but if we’re listening carefully, we can hear another sound behind the carols: a battle cry. The cry of a newborn King, invading a world at war with its Maker. In his article, Christmas Isn’t Safe. It’s Good., S. D. Smith writes: “The incarnation is the invasion of…

    timeforprovidence.com/2025/12/

  5. Sunday Special: Hosting in the UK vs. The North Pole

    Welcome to Day 21 of the MyWebHost Advent Calendar! 🇬🇧 It is the final Sunday before Christmas. The presents are wrapped 🎁, the turkey is bought 🤞, and the panic is starting to subside. But before we relax, we need to talk about Geography. When you buy web hosting, you aren't buying a "Cloud" that floats in the sky above your house. You are renting a physical metal box sitting in a cold room somewhere on planet Earth. For many budget hosting providers, that "somewhere" is […]

    mywebhost.co.uk/hosting-types/

  6. Keeping the Sleigh Airworthy: The Truth About Managed Hosting

    Welcome to Day 20 of the MyWebHost Advent Calendar! 🛷 We are in the final countdown to Christmas. Over the last 19 days, we have learned how to build, secure, and optimise a website. Your sleigh is built. But here is the million-pound question: Is it actually airworthy? You might be looking at your To-Do list—Update Plugins, Check Backups, Monitor Uptime, Audit Security—and thinking: "I am a toy maker, not a mechanic. I don't know how to fix the landing gear if it snaps […]

    mywebhost.co.uk/hosting-types/

  7. The Local Sleigh Route: Mastering Local SEO

    Welcome to Day 18 of the MyWebHost Advent Calendar! 📍 Yesterday, we learned how to tell compelling stories with a blog to capture interest globally. Today, we need to bring it back home. We need to make sure Santa knows exactly where to park the sleigh. Imagine Santa is flying high over the UK on Christmas Eve. He checks his Sat Nav. Below him, amidst the millions of rooftops, he sees two houses: House A: Has a bright neon sign on the roof saying "SMITH FAMILY HERE," its precise GPS […]

    mywebhost.co.uk/seo/the-local-

  8. The Local Sleigh Route: Mastering Local SEO

    Welcome to Day 18 of the MyWebHost Advent Calendar! 📍 Yesterday, we learned how to tell compelling stories with a blog to capture interest globally. Today, we need to bring it back home. We need to make sure Santa knows exactly where to park the sleigh. Imagine Santa is flying high over the UK on Christmas Eve. He checks his Sat Nav. Below him, amidst the millions of rooftops, he sees two houses: House A: Has a bright neon sign on the roof saying "SMITH FAMILY HERE," its precise GPS […]

    mywebhost.co.uk/seo/the-local-

  9. Wrapping the Gift: On-Page SEO (Titles & Headings)

    Welcome to Day 15 of the MyWebHost Advent Calendar! 🎁 We are deep into Week 3: Content & SEO. Behind Door 13, we found the right toy (Keyword Research) by reading Santa's letters. Behind Door 14, we built the shop (Core Pages) to display our wares. Now, we need to wrap the gift. Imagine you buy a beautiful diamond ring for your partner. You don't just hand it to them in a crinkled plastic carrier bag. You put it in a velvet box, wrap it in silver paper, add a bow, and attach a […]

    mywebhost.co.uk/seo/wrapping-t

  10. Reading Santa’s Letters: Keyword Research Basics

    Welcome to Day 13 of the MyWebHost Advent Calendar! 📜 We have entered Week 3: Content & SEO. We have built the workshop, tuned the engine with NVMe, and prepared for traffic spikes. Now, we need to fill the shelves. But what toys should the elves actually make? Imagine Santa sitting in his workshop in November. He doesn't just guess what children want. He doesn't decide to manufacture 50,000 wooden ducks just because he personally likes wooden ducks. If he did that, Christmas morning […]

    mywebhost.co.uk/seo/reading-sa

  11. Day 12 of my stitchy advents was a really good one. CCS included a stunning pair of iridescent stork stitching scissors, and BNS added a beautiful fabric that’s already sparking ideas for future projects. A joy to open today.

    #stitchyadvent #moonlitdyeworks #crossstitch #adventcalendar #advent2025

  12. Day 12 of my stitchy advents was a really good one. CCS included a stunning pair of iridescent stork stitching scissors, and BNS added a beautiful fabric that’s already sparking ideas for future projects. A joy to open today.

    #stitchyadvent #moonlitdyeworks #crossstitch #adventcalendar #advent2025

  13. Day 11 of my stitchy advents brought a fun mix of creative and cozy. CCS included another holiday pattern, and BNS added some festive treats that have me planning a little baking next weekend. Loving how these calendars blend stitching and seasonal fun.

    #stitchyadvent #moonlitdyeworks #crossstitch #adventcalendar #advent2025

  14. Day 11 of my stitchy advents brought a fun mix of creative and cozy. CCS included another holiday pattern, and BNS added some festive treats that have me planning a little baking next weekend. Loving how these calendars blend stitching and seasonal fun.

    #stitchyadvent #moonlitdyeworks #crossstitch #adventcalendar #advent2025

  15. Good morning to you! Here are today’s #Advent2025 readings:

    Psalm 146:5-10; Ruth 1:6-18; 2 Peter 3:1-10

    The link to Bible Gateway: December 11, 2025 biblegateway.com/passage/?sear

  16. Good morning to you! Here are today’s #Advent2025 readings:

    Psalm 146:5-10; Ruth 1:6-18; 2 Peter 3:1-10

    The link to Bible Gateway: December 11, 2025 biblegateway.com/passage/?sear

  17. Today’s #Advent2025 readings if you’re following along:

    Psalm 21; Genesis 15:1-18; Matthew 12:33-37

    Here’s a link to the readings at Bible Gateway. biblegateway.com/passage/?sear

  18. Today’s #Advent2025 readings if you’re following along:

    Psalm 21; Genesis 15:1-18; Matthew 12:33-37

    Here’s a link to the readings at Bible Gateway. biblegateway.com/passage/?sear

  19. Today’s #Advent2025 readings, if you’re following along…

    Psalm 21; Isaiah 41:14-20; Romans 15:14-21

    A link to open the readings on Bible Gateway biblegateway.com/passage/?sear

  20. Today’s #Advent2025 readings if you’re following along:

    Psalm 21; Isaiah 24:1-16a; 1 Thessalonians 4:1-12

  21. The readings for the Second Sunday of Advent, if you’re reading along…

    Isaiah 11:1-10, Psalm 72:1-7, 18-19, Romans 15:4-13, Matthew 3:1-12

    #Advent2025

  22. Advent Calendar – 2025 – Detail Dialog – Part 2

    Client contract from a UI perspective

    In this project, the user interface not only serves as a graphical layer on top of the backend, but is also part of the overall contract between the user, the client, and the server. This part focuses on the data flow from the UI’s perspective: how inputs are translated into structured requests, how the client forwards them, and what feedback the user interface processes.

    1. Client contract from a UI perspective
    2. Client layer (URLShortenerClient): Extensions
    3. Server API and Handler
    4. Persistence and Store Implementations
      1. InMemory Implementation
      2. EclipseStore Implementation
      3. Uniformity and Compatibility
      4. Advantages of the approach
    5. Domain Model and Defaults
      1. ShortUrlMapping as a central link
      2. DefaultValues – Central System Constants
      3. AliasPolicy with Logging
    6. JSON serialisation and deserialization
      1. Extending Serialisation in JsonUtils
      2. Extending Deserialization
      3. Purge of incoming JSON data
      4. Optimisation of JSON output
      5. Consistency and interoperability
    7. Security and robustness in the UI flow
      1. Input validations in the generation dialogue
      2. Defensive navigation in the detail dialogue
      3. Dealing with the Clipboard API
      4. Consistent feedback and fault tolerance
    8. Result

    The source code for this version can be found on GitHub at https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-03

    Here’s the screenshot of the version we’re implementing now.

    The basis of the contract is the class ShortenRequest, which was extended in this development step with the new field expiresAt. This field serves as a central repository for expiration dates and is entirely optional – meaning existing clients will continue to function even without this attribute. The UI client is thus both backwards-compatible and future-proof.

    public class ShortenRequest {  private String url;  private String shortURL;  private Instant expiresAt;  public ShortenRequest(String url, String shortURL, Instant expiresAt) {    this.url = url;    this.shortURL = shortURL;    this.expiresAt = expiresAt;  }  public Instant getExpiresAt() { return expiresAt; }  public void setExpiresAt(Instant expiresAt) { this.expiresAt = expiresAt; }}

    The CreateView passes this object to the URLShortenerClient, which handles communication with the server. The decisive factor here is that the user interface does not establish HTTP connections directly but delegates this task to a dedicated client component. This keeps the UI lean and testable, while the client is centrally responsible for logging, error handling and request generation. The central interface is the createCustomMapping method, which maps the extended contract:

    public ShortUrlMapping createCustomMapping(String alias, String url, Instant expiredAt) throws IOException {  logger().info("Create custom mapping alias='{}' url='{}' expiredAt='{}'", alias, url, expiredAt);  URL shortenUrl = serverBaseAdmin.resolve(PATH_ADMIN_SHORTEN).toURL();  HttpURLConnection connection = (HttpURLConnection) shortenUrl.openConnection();  connection.setRequestMethod("POST");  connection.setDoOutput(true);  connection.setRequestProperty(CONTENT_TYPE, JSON_CONTENT_TYPE);  var shortenRequest = new ShortenRequest(url, alias, expiredAt);  String body = shortenRequest.toJson();  try (OutputStream os = connection.getOutputStream()) {    os.write(body.getBytes(UTF_8));  }  int status = connection.getResponseCode();  if (status == 200 || status == 201) {    try (InputStream is = connection.getInputStream()) {      String jsonResponse = new String(is.readAllBytes(), UTF_8);      ShortUrlMapping shortUrlMapping = fromJson(jsonResponse, ShortUrlMapping.class);      return shortUrlMapping;    }  }  if (status == 409) {    throw new IllegalArgumentException("Alias already in use");  }  throw new IOException("Unexpected status: " + status);}

    This example illustrates how precisely the UI and the client are coordinated. The UI passes a fully populated domain instance (ShortenRequest) that contains all the required fields. The client handles the serialisation, performs the communication, and returns a ShortUrlMapping in response. The UI then displays the relevant data immediately.

    A central design principle in this interaction is “data instead of commands”. The UI does not send specific control commands, but always sends complete data objects. The backend decides how to operate based on these objects. This decoupling has several advantages:

    1. Extensibility: New fields (e.g. expiresAt) can be added without breaking existing APIs.
    2. Traceability: Every operation is fully traceable via the Request object.
    3. Security: The client can validate inputs before converting them into HTTP requests.

    In the UI, the client’s responses are used to provide feedback and update the current view. Not only is the shortcode displayed, but the entire mapping object is also displayed, which already contains all the values calculated on the server side. The user interface thus always remains in line with the actual system state.

    This pattern – a clear contract structure between UI, client and server – creates stability and maintainability in the long term. Changes to the backend API do not require adjustments to the UI logic, as long as the underlying data contract remains unchanged. This establishes a binding communication path that enables technical evolution without impairing user interaction.

    Client layer (URLShortenerClient): Extensions

    The client layer forms the technical bridge between the user interface and the server. It translates data objects into HTTP requests, monitors the communication, and transfers the results back into domain objects. This chapter shows how the existing class URLShortenerClient has been extended to support the new expiration concept (expiresAt) while ensuring clean, valid communication with the server.

    The starting point was the existing function createCustomMapping(String alias, String url), which an overloaded variant has now supplemented. This accepts an additional expiration date (Instant expiredAt) and performs all necessary steps to transfer the data to the server in a complete and compliant manner.

    public ShortUrlMapping createCustomMapping(String alias, String url, Instant expiredAt)    throws IOException {  logger().info("Create custom mapping alias='{}' url='{}' expiredAt='{}'", alias, url, expiredAt);  if (alias != null && !alias.isBlank()) {    var validate = AliasPolicy.validate(alias);    if (!validate.valid()) {      var reason = validate.reason();      throw new IllegalArgumentException(reason.defaultMessage);    }  }  URL shortenUrl = serverBaseAdmin.resolve(PATH_ADMIN_SHORTEN).toURL();  HttpURLConnection connection = (HttpURLConnection) shortenUrl.openConnection();  connection.setRequestMethod("POST");  connection.setDoOutput(true);  connection.setRequestProperty(CONTENT_TYPE, JSON_CONTENT_TYPE);  var shortenRequest = new ShortenRequest(url, alias, expiredAt);  String body = shortenRequest.toJson();  logger().info("createCustomMapping - body - '{}'", body);  try (OutputStream os = connection.getOutputStream()) {    os.write(body.getBytes(UTF_8));  }  int status = connection.getResponseCode();  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)     throw new IllegalArgumentException("Alias already in use");  }  throw new IOException("Unexpected status: " + status);}

    With this method, the extended expiration date is seamlessly integrated into the existing communication flow. The UI calls this method via CreateView, keeping the extension transparent to the user – the new functionality is immediately available without changing the user experience.

    In addition to improving the method signature, the consistency of HTTP communication has also been improved. Instead of manually setting headers, the constant JSON_CONTENT_TYPE is now used consistently to avoid format errors and ensure unique typing. This standardisation reduces the risk of inconsistent requests and facilitates later protocol extensions (e.g., for authenticating or signing requests).

    Another critical point is logging. The URLShortenerClient logs all relevant steps – from request creation to response processing. This transparency is crucial for understanding the exact process in the event of an error. Especially during the development phase and when integrating new features such as expiresAt, logging provides valuable insights into the timing, format and status of the data transfer.

    A typical log snippet might look like this:

    INFO Create custom mapping alias='test123' url='https://example.com' expiredAt='2025-12-31T23:59:00Z'INFO createCustomMapping - body - '{"url": "https://example.com", "alias": "test123", "expiresAt": "2025-12-31T23:59:00Z"}'INFO createCustomMapping - jsonResponse - '{"shortCode": "ex-9A7", "originalUrl": "https://example.com", "expiresAt": "2025-12-31T23:59:00Z"}'INFO shortUrlMapping.. ShortUrlMapping[shortCode=ex-9A7, url=https://example.com, expiresAt=2025-12-31T23:59:00Z]

    This structured logging follows a clear pattern that makes all the steps involved traceable. It is intended not only for debugging, but also for integration into an audit or monitoring solution in the long term.

    In conclusion, it should be noted that the client layer is now fully context-sensitive. It recognises optionally passed expiration dates, validates inputs, communicates clearly structured JSON payloads, and correctly interprets server-side responses. This creates a robust, well-defined interface between the user interface and the backend that can easily accommodate future extensions, such as custom policies or metadata.

    Server API and Handler

    The server layer forms the backbone of the entire architecture. It receives requests, interprets the data structures, and coordinates the creation, storage, or deletion of short links. With the introduction of the expiration date (expiresAt), the API has been extended by a significant semantic dimension. The goal was to integrate this new information into the existing request-response flow without causing compatibility issues with older clients.

    The central point of contact for incoming POST requests to create a short link is the ShortenHandler. This handles the JSON payload, performs validations, and interacts with the UrlMappingStore. In doing so, the processing has been extended to correctly extract the expiration date from the JSON object and pass it to persistence.

    final String body = readBody(ex.getRequestBody());ShortenRequest req = fromJson(body, ShortenRequest.class);if (isNullOrBlank(req.getUrl())) {  writeJson(ex, BAD_REQUEST, "Missing 'url'");  return;}final Result<ShortUrlMapping> urlMappingResult =    store.createMapping(req.getShortURL(), req.getUrl(), req.getExpiresAt());urlMappingResult  .ifPresentOrElse(success -> logger().info("mapping created success {}", success.toString()),                   failed -> logger().info("mapping created failed - {}", failed));urlMappingResult  .ifSuccess(mapping -> {    final Headers h = ex.getResponseHeaders();    h.add("Location", "/r/" + mapping.shortCode());    writeJson(ex, fromCode(201), toJson(mapping));  })  .ifFailure(errorJson -> {    try {      var parsed = JsonUtils.parseJson(errorJson);      var errorCode = Integer.parseInt(parsed.get("code"));      var message = parsed.get("message");      writeJson(ex, fromCode(errorCode), message);    } catch (Exception e) {      writeJson(ex, CONFLICT, errorJson);    }  });

    The ShortenHandler deliberately does not use a framework, but works with native Java APIs (HttpExchange, HttpURLConnection) to keep the control flow completely transparent. This decision is not only for traceability but also enables a precise understanding of how HTTP works at the lowest level. The code clearly shows the steps of the server cycle: read body, deserialise, validate, invoke domain logic, and write response.

    Another example is the ListHandler, which has been slightly modified to take advantage of the modern Sequenced API (List.getFirst()), thus increasing readability:

    private static String first(Map<String, List<String>> q, String key) {  var v = q.get(key);  return (v == null || v.isEmpty()) ? null : v.getFirst();}

    Special attention is paid to robust JSON data processing. Here, it has been ensured that line breaks and escape sequences do not cause parsing problems. In the JsonUtils module,  a cleanup step was introduced before parsing:

    s = s.replaceAll(“\\n”, “”);

    This prevents multi-line JSON data from leading to errors – a typical stumbling block for APIs that process manually generated or logged payloads.

    Overall, the server API deliberately remains flat and declarative. Each operation represents a clearly defined domain action; there is no excessive branching or hidden state change. By adding expiresAt, this style is retained, and the system continues to react deterministically: A request creates a mapping, optionally with an expiration date, and returns the complete record as JSON.

    This simplicity is not a coincidence, but an expression of a design principle that runs through all levels of the application: explicit data flows instead of implicit magic. The result is a system that remains understandable for both users and developers and can be reliably expanded.

    Persistence and Store Implementations

    The persistence layer is the foundation on which the entire system’s reliability rests. With the introduction of the expiration date (expiresAt), it had to be expanded accordingly so that this new information can be reliably stored, queried and evaluated – regardless of whether the data is stored in memory or in a persistent database such as EclipseStore.

    At the center of this layer is the UrlMappingUpdater interface, which has been extended by a new method. This method adds the Instant expiredAt parameter to the previous signatures, so that the persistence layer can now explicitly handle expiration times.

    public interface UrlMappingUpdater {  Result<ShortUrlMapping> createMapping(String originalUrl);  Result<ShortUrlMapping> createMapping(String alias, String url);  Result<ShortUrlMapping> createMapping(String alias, String url, Instant expiredAt);  boolean delete(String shortCode);}

    This clearly states that every implementation must process flow information. This adaptation follows the principle of contract-based design – the interface defines which capabilities the specific implementation must possess without prescribing their technical details.

    InMemory Implementation

    The first customization was done in the InMemoryUrlMappingStore. This class is primarily used for tests and volatile runtime environments and stores all mappings in a ConcurrentHashMap. By extending the createMapping methods, expiration data is now correctly transferred to the MappingCreator.

    @Overridepublic Result<ShortUrlMapping> createMapping(String alias, String originalUrl, Instant expiredAt) {  logger().info("alias: {} - originalUrl: {} - expiredAt: {} ", alias, originalUrl, expiredAt);  return creator.create(alias, originalUrl, expiredAt);}

    In  the MappingCreator itself, the expiration time is integrated into the ShortUrlMapping and stored directly when created:

    public Result<ShortUrlMapping> create(String alias, String url, Instant expiredAt) {  logger().info("createMapping - alias='{}' / url='{}' / expiredAt='{}'", alias, url, expiredAt);  final String shortCode;  if (!isNullOrBlank(alias)) {    if (repository.containsKey(alias)) {      return Result.failure("Alias already exists");    }    shortCode = alias;  } else {    shortCode = generator.generate();  }  var mapping = new ShortUrlMapping(shortCode, url, Instant.now(clock), Optional.ofNullable(expiredAt));  store.accept(mapping);  return Result.success(mapping);}

    This pattern shows that expiration information, if any, is directly part of the ShortUrlMapping domain object. The code is deliberately simple: no additional state, no special treatment, but only an optional value.

    EclipseStore Implementation

    The EclipseStoreUrlMappingStore has also been adapted for permanent storage. The same principle applies here, but with a focus on long-term persistence.

    @Overridepublic Result<ShortUrlMapping> createMapping(String alias, String originalUrl, Instant expiredAt) {  logger().info("alias: {} - originalUrl: {} - expiredAt: {}", alias, originalUrl, expiredAt);  return creator.create(alias, originalUrl, expiredAt);}

    Tightly coupling with the same MappingCreator ensures complete consistency in behaviour between the InMemory and EclipseStore variants. The only difference is the storage duration: While the data in the InMemory store is lost on restart, it remains persistent in the EclipseStore.

    Uniformity and Compatibility

    A central goal of these adaptations was the complete equal treatment of all persistent species. Whether it’s testing, development mode, or production, all paths use the same objects and methods. This eliminates the risk of divergent logic across different storage types. Changes to the domain, such as the introduction of expiresAt, therefore only have to be implemented in one place.

    Advantages of the approach

    This consistent standardization brings several advantages:

    1. Transparency: Every mapping operation is traceable and documented in the log.
    2. Consistency: InMemory and EclipseStore stores behave identically.
    3. Extensibility: New storage mechanisms (e.g., SQL, key-value store, cloud) can be easily added as long as they fulfil the interface contract.

    With this extension, the persistence layer becomes the system’s reliable backbone. The expiration date is now a full-fledged part of the data model – precisely recorded, securely stored and retrievable at any time. This means that future functions such as automatic cleaning of expired entries or time-based statistics can be implemented directly on this basis.

    Domain Model and Defaults

    The class ShortenRequest has been extended to include the field expiresAt. This field allows you to pass an optional expiration time, which is set by the user in the UI and delivered to the server as an instant in JSON format. This information thus becomes a full-fledged component of the data model and can be further processed at both the transport layer and the persistence layer.

    public class ShortenRequest {  private String url;  private String shortURL;  private Instant expiresAt;  public ShortenRequest(String url, String shortURL, Instant expiresAt) {    this.url = url;    this.shortURL = shortURL;    this.expiresAt = expiresAt;  }  public Instant getExpiresAt() { return expiresAt; }  public void setExpiresAt(Instant expiresAt) { this.expiresAt = expiresAt; }  public String toJson() {    var a = shortURL == null ? "\"null\"" : "\"" + JsonUtils.escape(shortURL) + "\"";    var b = expiresAt == null ? "\"null\"" : "\"" + JsonUtils.escape(expiresAt.toString()) + "\"";    return """        {          \"url\": \"%s\",          \"alias\": %s,          \"expiresAt\": %s        }        """.formatted(JsonUtils.escape(url), a, b);  }}

    It is important to note that the expiresAt value is not mandatory. This keeps existing clients, and server calls compatible, even if they don’t set the field. The domain model was deliberately designed to remain backwards-compatible and extensible, an essential principle when introducing new features.

    ShortUrlMapping as a central link

    The ShortUrlMapping class represents the central data element between the client and the server. It contains all the relevant information of a short link: the generated shortcode, the destination URL, the creation date and, optionally, the expiration date. By using Optional<Instant>, the possible absence of an expiration date is explicitly modelled.

    public record ShortUrlMapping(String shortCode, String originalUrl, Instant createdAt, Optional<Instant> expiresAt) { }

    This decision underscores the model’s functional properties: an immutable data element that is fully defined only when it is created. Changes to a mapping are always made through new creations or explicit updates – never through silent mutations.

    DefaultValues – Central System Constants

    In addition to the model classes, the DefaultValues class has also been extended. It contains constants that are used throughout the application, especially the base URL for generated short links.

    public final class DefaultValues {  TODO - must be editable by user  public static final String SHORTCODE_BASE_URL = "https://3g3.eu/";  public static final int ADMIN_SERVER_PORT = 9090;  public static final String ADMIN_SERVER_HOST = "localhost";  public static final String ADMIN_SERVER_PROTOCOL = "http";  more path definitions ...}

    The constant SHORTCODE_BASE_URL serves as the basic component for generating and displaying short links in the user interface. Although it is currently statically defined, it has already been noted that it will be dynamically configurable in a future iteration. This lays the foundation for flexible deployment scenarios in which different environments (e.g., development, test, production) can use their own base URLs.

    AliasPolicy with Logging

    Another component of the domain model is the AliasPolicy, which defines rules for valid aliases. Logging has been added as part of the enhancements to make the validation processes easier to understand:

    public final class AliasPolicy implements HasLogger {  public static Validation validate(String alias) {    HasLogger.staticLogger().info("validate - {}", alias);    if (alias == null || alias.isBlank()) return Validation.fail(Reason.NULL_OR_BLANK);    if (alias.length() < MIN) return Validation.fail(Reason.TOO_SHORT);    if (alias.length() > MAX) return Validation.fail(Reason.TOO_LONG);    if (! ALLOWED_PATTERN.matcher(alias).matches()) return Validation.fail(Reason.INVALID_CHARS);    return Validation.success();  }}

    This logging makes faulty aliases immediately visible, making troubleshooting the interaction between the UI and the backend much easier.

    With these adjustments, the domain model becomes a robust, clearly structured core of the application. The central entity ShortUrlMapping fully reflects the real-world state of a shortlink, while ShortenRequest controls the creation of new entries and provides system-wide constants to DefaultValues. All extensions remain consistent with the original design principle: simple, functional structures that precisely define what a user can create, modify, or retrieve.

    JSON serialisation and deserialization

    The reliability of communication between the components of an application depends crucially on the quality of the serialisation layer. This chapter describes how JSON processing has been extended and stabilised to safely transport the new expiresAt field while improving code readability and error tolerance.

    Extending Serialisation in JsonUtils

    The JsonUtils class  forms the backbone of JSON processing in the project. It provides both generic helper methods and specific serialisation routines for the most critical domain objects. With the introduction of the expiration date, it was necessary to ensure this field was correctly integrated into JSON documents without affecting legacy data formats.

    In the method for serialising ShortenRequest, the field expiresAt has therefore been  added:

    if (dto instanceof ShortenRequest req) {  Map<String, Object> m = new LinkedHashMap<>();  m.put("url", req.getUrl());  m.put("alias", req.getShortURL());  m.put("expiresAt", req.getExpiresAt());  return toJson(m);}

    This change will automatically include the expiration date, if it exists. If it is not set, the field appears as null in the JSON  and thus remains syntactically valid. This explicit representation of null values improves readability and allows the server to clearly distinguish between “not set” and “deliberately empty”.

    Extending Deserialization

    Analogous to serialisation, deserialization has also been extended to read expiresAt from JSON data correctly. In the fromJson method, the customisation is done:

    if (type == ShortenRequest.class) {  String url = m.get("url");  String alias = m.get("alias");  Instant expiresAt = parseInstantSafe(m.get("expiresAt"));  return (T) new ShortenRequest(url, alias, expiresAt);}

    The parseInstantSafe function converts an ISO-8601 string to an instant object and handles invalid or empty values gracefully. This error resistance is significant for clients that may send different or older JSON structures.

    Purge of incoming JSON data

    A common problem with APIs is reading in JSON data that contains unintentional line breaks or extra escape characters. To prevent parsing errors,  simple preprocessing has been introduced in JsonUtils.parseJson:

    s = s.replaceAll(“\\n”, “”);

    This step removes all line breaks before the parser runs. This reliably recognises and correctly interprets both manually formatted JSON files and logged messages. This customisation makes the system more robust against inconsistent formatting that is common in real-world environments. (At this point, however, I am aware that it is far from sufficient…)

    Optimisation of JSON output

    As part of these changes, the toJsonListing method has also been revised. Instead of a complicated StringBuilder structure, a simple, readable string concatenation is now used:

    return "{" +    "\"mode\":\"" + escape(mode) + "\"," +    "\"count\":" + count + "," +    "\"items\":" + toJsonArrayOfObjects(items) +    "}";

    This simplification reduces the susceptibility to errors and makes debugging easier. Especially in systems that do not require frameworks, code readability is a decisive factor for maintainability and error diagnosis.

    Consistency and interoperability

    An essential aspect of the revision of serialisation was maintaining interoperability across different clients and API versions. Since all fields are still serialised on a string-by-string basis and the JSON structure is explicit, the data exchange remains fully compatible even with older clients. This means that a client that does not send expiresAt will be accepted by the server, and a server that does not expect the field will ignore it.

    This loose coupling between transmitter and receiver is a central design goal of the project. It allows incremental expansions without updating all components at once.

    The revision to JSON processing strengthens the application’s robustness and future-proofing. By specifically extending JsonUtils to include the expiresAt field, cleaning incoming data, and simplifying the output, serialisation is now both technically stable and semantically precise. It thus meets the requirements of a modern, evolvable interface that remains clearly comprehensible for both automated processes and human readers.

    Security and robustness in the UI flow

    As the complexity of the user interface grows, so does the responsibility to ensure that all interactions remain predictable, valid, and stable. This chapter explains how security considerations and robustness were built directly into the UI flow – from input validation to defensive navigation decisions to dealing with external browser APIs.

    Input validations in the generation dialogue

    The most important security aspect in the UI is validating user input. As soon as a new short link is created, the application checks whether the entered URL contains a valid scheme. This prevents potential attacks by manipulated or unsupported protocols at an early stage.

    binder.forField(urlField)    .asRequired("URL must not be empty")    .withValidator(url -> url.startsWith("http://") || url.startsWith("https://"),                   "Only HTTP(S) URLs allowed")    .bind(ShortenRequest::getUrl, ShortenRequest::setUrl);

    This simple validation only accepts URLs that can be reached via secure or well-defined transport protocols. This protects both the user and the application from unwanted interactions with unsafe targets. However, it is not yet full input validation.

    Another part of the validation concerns the expiration date. Here, it is ensured that a selected point in time is always in the future:

    if (exp.isPresent() && exp.get().isBefore(Instant.now())) {  Notification.show("Expiry must be in the future");  return false;}

    This mechanism protects against incorrect entries and inconsistent data states, especially when users edit the form multiple times or select times that have already expired.

    Defensive navigation in the detail dialogue

    The detail dialogue (DetailsDialog) also follows the principle of secure interaction. If a saved URL is opened via the “Open” button, this is done exclusively by calling the method UI.getCurrent().getPage().open(), but only if the destination is clearly recognised as an HTTP or HTTPS link. This prevents internal or local resources from being accidentally or intentionally called via the UI.

    openBtn.addClickListener(_ -> {  if (originalUrl.startsWith("http://") || originalUrl.startsWith("https://")) {    fireEvent(new OpenEvent(this, shortCode, originalUrl));    getUI().ifPresent(ui -> ui.getPage().open(originalUrl, "_blank"));  } else {    Notification.show("Invalid URL scheme");  }});

    This decision strengthens the separation between internal and external resources. User actions are controlled to prevent unwanted side effects outside the application.

    Dealing with the Clipboard API

    Another security-relevant topic is how to use the native Clipboard API. For data protection reasons, this is only available in secure browser contexts, i.e. via HTTPS or localhost. The application uses this API to copy shortcodes and URLs to the clipboard conveniently. If access is not allowed in the current context, the call does not result in an error, but is silently discarded – an example of defensive programming behaviour in the UI.

    UI.getCurrent().getPage().executeJs(“navigator.clipboard.writeText($0)”, SHORTCODE_BASE_URL + m.shortCode());

    This non-blocking call avoids JavaScript errors and keeps the UI stable even if the browser rejects the action. The application always responds in a controlled manner and remains in a valid state.

    Consistent feedback and fault tolerance

    A core element of robustness is the feedback system. Every user command – whether successful or incorrect – triggers visual feedback. The application consistently uses the Vaadin notification component for this purpose. It provides information about validation errors, successful copying operations and system messages without interrupting the workflow. This asynchronous reporting system supports the idea that a user can continue at any time, even if a single operation fails.

    Notification.show(“Alias already assigned or error saving”, 3000, Notification.Position.MIDDLE);

    This type of error communication avoids frustration and contributes to perceived stability. The user remains informed, but never blocked.

    The measures described in this chapter – input validations, defensive navigation, and safe clipboard use – share a common goal: robustness through caution. Every action in the user interface is checked, every external interface is secured, and every user interaction is clearly reported. This allows the UI to achieve a high level of error tolerance without sacrificing ease of use. This balance of security and user-friendliness concludes the technical implementation of the detailed dialogue. It lays the foundation for the upcoming enhancements, in which security and user experience will continue to go hand in hand.

    Result

    With today’s Advent calendar, the application has reached a decisive degree of maturity. While the first parts mainly laid the structural and functional foundations, today it is all about the depth of detail and the quality of interaction. The focus was on transitioning from a technically functional interface to a well-thought-out, user-centric application.

    The newly introduced detail dialogue marks a central turning point: it allows viewing individual entries in context without leaving the overview. This concept combines technical clarity with ease of use, creating a modular structure that can easily accommodate future expansions. By using events to decouple the components, the architecture remains clean, traceable and testable.

    The integration of the expiration date is also proving to be a milestone in the system’s functional development. From input in the UI to the client layer to persistence and JSON processing, the new parameter has been consistently integrated into all layers. The original design was deliberately kept simple – each layer knows only its own responsibility, and none contains logic that anticipates another layer. The result is a consistent, precise data flow that combines technical precision with semantic transparency.

    Another critical advance is improving the user experience (UX). Consistent interaction patterns, precise feedback, and security-conscious behaviour make the application both intuitive and trustworthy. The combination of immediate feedback, non-blocking error handling, and a harmonious visual-functional design shows how technological rigour and user focus can go hand in hand.

    From an architectural point of view, this part of the Advent calendar illustrates that even in small projects, cleanliness, coherence, and expandability are the key success factors. The clear separation between UI, client, server, and persistence not only enables efficient maintenance but also opens the way for future modules – such as administrative views, bulk operations, or security policies for user groups.

    Cheers Sven

    #Advent2025 #EclipseStore #Java #Vaadin

  23. Advent Calendar – 2025 – Detail Dialog – Part 1

    Classification and objectives from a UI perspective

    Today’s Advent Calendar Day focuses specifically on the interaction level prepared in the previous parts. While the basic structure of the user interface and the layout were defined at the beginning, and the interactive table view with sorting, filtering and dynamic actions was subsequently established, it is now a matter of making the transition from overview to detailed observation consistent. The user should no longer only see a tabular collection of data points, but should receive a view tailored to the respective object that enables contextual actions.

    1. Classification and objectives from a UI perspective
    2. OverviewView: Interactive enhancements
    3. Detail view as a standalone UI component (DetailsDialog)
    4. CreateView: Expiration date in the UI
    5. Navigation and package structure
    6. Interaction patterns and UX coherence
      1. Uniform copying behaviour
      2. Context menus and multiple interactions
      3. Consistency of feedback
      4. HTTPS Requirement for Clipboard

    The source code for this version can be found on GitHub at https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-03

    Here’s the screenshot of the version we’re implementing now.

    The central goal of this chapter is to improve the user experience without sacrificing the simplicity of what has been done so far. The interface is extended with a detailed view implemented as a standalone dialogue, thereby deliberately creating a clear framework between the overall list and the individual object. This decision follows the principle of the cognitive separation of information spaces: an overview serves to orient and find relevant entries. At the same time, the detailed view creates an isolated, focused working environment.

    From an architectural point of view, this extension is an essential step towards a component-oriented UI in which each functional unit – such as creating, displaying or deleting short links – is represented by its own, clearly defined components. The new dialogue includes all the necessary UI elements, validations, and event flows for displaying and interacting with a single ShortUrlMapping object. This decoupling not only enables better testability and reusability but also reduces the cognitive overhead in the primary grid, which previously had to record all interactions directly.

    The dialogue serves as an interactive interface between the visual representation and the underlying data model. It reflects the current object, provides copy and navigation actions, and provides visual feedback on the entry’s status – for example, via a colour-coded expiration date. This modular structure allows the user to check details, perform actions, or remove erroneous entries without losing the application’s context.

    OverviewView: Interactive enhancements

    With the third expansion stage of the user interface, the previous list view is not only expanded, but also functionally upgraded. The OverviewView forms the foundation of the daily work with the created short links and is supplemented in this step by several mechanisms that make the behaviour of the application more natural and efficient.

    A central aspect concerns the reactivity of the search fields. In previous versions, search queries were triggered immediately after each input, which was technically correct but inefficient in practice, using the ValueChangeMode.LAZY, in combination with a 400-millisecond delay, the system does not react until the user has completed their input. This delay serves as a natural “pause for thought” and prevents unnecessary updates to the grid. In addition, the enter-key flow has been activated so that a targeted search confirmation can be performed via the keyboard – a typical usage pattern in the professional environment.

    codePart.setValueChangeMode(ValueChangeMode.LAZY);codePart.setValueChangeTimeout(400);codePart.addValueChangeListener(e -> refresh());urlPart.setValueChangeMode(ValueChangeMode.LAZY);urlPart.setValueChangeTimeout(400);urlPart.addValueChangeListener(e -> refresh());

    A second improvement concerns the variety of interactions in the grid. Instead of acting exclusively via buttons, the user can now open a data record by double-clicking or pressing Enter. This form of interaction is not only faster, but also meets the expectations you are used to from desktop applications. To further simplify operation, a context menu has been added that automatically offers the appropriate actions when right-clicking: View details, open target URL, copy shortcode, or delete the entry.

    grid.addItemDoubleClickListener(ev -> openDetailsDialog(ev.getItem()));grid.addItemClickListener(ev -> {  if (ev.getClickCount() == 2) openDetailsDialog(ev.getItem());});GridContextMenu<ShortUrlMapping> menu = new GridContextMenu<>(grid);menu.addItem("Show details", e -> e.getItem().ifPresent(this::openDetailsDialog));menu.addItem("Open URL", e -> e.getItem().ifPresent(m -> UI.getCurrent().getPage().open(m.originalUrl(), "_blank")));menu.addItem("Copy shortcode", e -> e.getItem().ifPresent(m -> UI.getCurrent().getPage().executeJs("navigator.clipboard.writeText($0)", m.shortCode())));menu.addItem("Delete...", e -> e.getItem().ifPresent(m -> confirmDelete(m.shortCode())));

    In addition to functionality, the grid column layout has been revised. The shortcode is now displayed in a monospace font, which makes it easier to quickly grasp and compare visually. In addition, a small copy symbol allows copying the entire short link to the clipboard with a mouse click. JavaScript is used to call up the browser’s native clipboard service.

    grid.addComponentColumn(m -> {  var code = new Span(m.shortCode());  code.getStyle().set("font-family", "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace");  var copy = new Button(new Icon(VaadinIcon.COPY));  copy.addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE);  copy.getElement().setProperty("title", "Copy ShortUrl");  copy.addClickListener(_ -> {    UI.getCurrent().getPage().executeJs("navigator.clipboard.writeText($0)", SHORTCODE_BASE_URL + m.shortCode());    Notification.show("Shortcode copied");  });  var wrap = new HorizontalLayout(code, copy);  wrap.setSpacing(true);  wrap.setPadding(false);  return wrap;}).setHeader("Shortcode").setAutoWidth(true).setFrozen(true).setResizable(true).setFlexGrow(0);

    In the column with the original URL, the layout has been changed to an elliptical display : Long URLs are truncated, but remain fully visible via the tooltip. This detail improves readability without losing information.

    grid.addComponentColumn(m -> {  var a = new Anchor(m.originalUrl(), m.originalUrl());  a.setTarget("_blank");  a.getStyle()      .set("white-space", "nowrap")      .set("overflow", "hidden")      .set("text-overflow", "ellipsis")      .set("display", "inline-block")      .set("max-width", "100%");  a.getElement().setProperty("title", m.originalUrl());  return a;}).setHeader("URL").setFlexGrow(1).setResizable(true);

    Particularly noteworthy is the new Expires column, which colour-coded status indicators have visually supplemented. These are based on Lumo badges and show the remaining time until a link expires: green for active, yellow for expiring soon, and red for expired.

    grid.addComponentColumn(m -> {  var pill = new Span(m.expiresAt()      .map(ts -> {        var days = Duration.between(Instant.now(), ts).toDays();        if (days < 0) return "Expired";        if (days == 0) return "Today";        return "in " + days + " days";      }).orElse("No expiry"));  pill.getElement().getThemeList().add("badge pill small");  m.expiresAt().ifPresent(ts -> {    long d = Duration.between(Instant.now(), ts).toDays();    if (d < 0) pill.getElement().getThemeList().add("error");    else if (d <= 3) pill.getElement().getThemeList().add("warning");    else pill.getElement().getThemeList().add("success");  });  return pill;}).setHeader("Expires").setAutoWidth(true).setResizable(true).setFlexGrow(0);

    Finally, the display’s ergonomics have been improved. The grid now works with reduced vertical spacing (compact mode) while retaining its readability thanks to alternating line colours. The line height has been adjusted so that as many entries as possible remain visible even on smaller monitors without making the UI look overloaded.

    With these changes, the OverviewView is transformed from a purely management view into a central control tool that provides both a quick overview and deep interaction. This lays the foundation for integrating the detail dialogue – it complements this view with an object-related perspective, while the OverviewView continues to serve as the starting point for navigation.

    Detail view as a standalone UI component (DetailsDialog)

    Now that the overview list has been expanded with interactive functions, the next logical step is to introduce a standalone UI component focused on the display and management of individual datasets. The goal is to create a modular and reusable view that operates independently of the main view but communicates with it via clearly defined events.

    The dialogue is based on Vaadin’s Dialogue class and opens for each selected ShortUrlMapping object. In doing so, it reads out all relevant properties of the passed object – shortcode, target URL, creation time and, optionally, the expiration date. These values are presented in a clearly structured format, supplemented by action buttons to open, copy and delete the entry.

    The following excerpt shows the basic structure of the dialogue:

    public class DetailsDialog extends Dialog implements HasLogger {  public static final ZoneId ZONE = ZoneId.systemDefault();  private static final DateTimeFormatter DATE_TIME_FMT =      DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm").withZone(ZONE);  private final String shortCode;  private final String originalUrl;  private final Instant createdAt;  private final Optional<Instant> expiresAt;  private final TextField tfShort = new TextField("Shortcode");  private final TextField tfUrl = new TextField("Original URL");  private final TextField tfCreated = new TextField("Created on");  private final TextField tfExpires = new TextField("Expires");  private final Span statusPill = new Span();  private final Button openBtn = new Button("Open", new Icon(VaadinIcon.EXTERNAL_LINK));  private final Button copyShortBtn = new Button("Copy ShortURL", new Icon(VaadinIcon.COPY));  private final Button copyUrlBtn = new Button("Copy URL", new Icon(VaadinIcon.COPY));  private final Button deleteBtn = new Button("Delete...", new Icon(VaadinIcon.TRASH));  private final Button closeBtn = new Button("Close");  public DetailsDialog(ShortUrlMapping mapping) {    Objects.requireNonNull(mapping, "mapping");    this.shortCode = mapping.shortCode();    this.originalUrl = mapping.originalUrl();    this.createdAt = mapping.createdAt();    this.expiresAt = mapping.expiresAt();    setHeaderTitle("Details: " + shortCode);    setModal(true);    setDraggable(true);    setResizable(true);    setWidth("720px");    openBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);    deleteBtn.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);    var headerActions = new HorizontalLayout(openBtn, copyShortBtn, copyUrlBtn, deleteBtn);    getHeader().add(headerActions);    configureFields();    var form = new FormLayout();    form.add(tfShort, tfUrl, tfCreated, tfExpires, statusPill);    form.setColspan(tfUrl, 2);    add(form);    closeBtn.addClickListener(e -> close());    getFooter().add(closeBtn);    wireActions();  }

    It is already clear here that the dialogue has a high degree of independence. It initialises its contents directly from the passed data object and uses a FormLayout to structure the fields. The input fields are read-only by default because the dialogue is used primarily for display.

    The visual feedback on the expiration status is provided by a so-called status pill, which indicates in colour and text whether a short link is still active, about to expire or has already expired. This is done via a small helper method that provides a status description including a colour scheme:

    private status computeStatusText() {  return expiresAt.map(ts -> {    long d = Duration.between(Instant.now(), ts).toDays();    if (d < 0) return new Status("Expired", "error");    if (d == 0) return new Status("Expires today", "warning");    if (d <= 3) return new Status("Expires in " + d + " days", "warning");    return new Status("Valid(" + d + " days left)", "success");  }).orElse(new Status("No expiry", "contrast"));}

    This status calculation complements the visual feedback from the summary table and provides a consistent representation across the entire application.

    The real added value of the DetailsDialog lies in its event-orientation. Instead of the calling view (OverviewView) controlling all actions itself, the actions are defined as Vaadin events. This allows the dialogue to send signals such as OpenEvent, CopyShortcodeEvent, CopyUrlEvent or DeleteEvent to its environment without knowing what they mean:

    public static class DeleteEvent extends ComponentEvent<DetailsDialog> {  public final String shortCode;  public DeleteEvent(DetailsDialog src, String sc) {    super(src);    this.shortCode = sc;  }}

    In the OverviewView , these events are received and processed:

    var dlg = new DetailsDialog(item);dlg.addDeleteListener(ev -> confirmDelete(ev.shortCode));dlg.addOpenListener(ev -> logger().info("Open URL {}", ev.originalUrl));dlg.addCopyShortListener(ev -> logger().info("Copied shortcode {}", ev.shortCode));dlg.addCopyUrlListener(ev -> logger().info("Copied URL {}", ev.url));dlg.open();

    This creates an apparent decoupling between the presentation and application logic. Dialogue handles representation and interaction; the surrounding view determines how to respond to events.

    In summary, the DetailsDialog establishes an architectural principle that goes beyond the specific use case. The combination of a modular UI component, declarative events and a clearly defined data model not only makes the application more flexible, but also more maintainable in the long term. The user benefits from a coherent, clearly structured, detailed view, which makes working with individual entries much more convenient and transparent.

    CreateView: Expiration date in the UI

    With this step, the creation of new short links receives an important semantic addition: the optional expiration date. The aim is to precisely define the intended service life at the time of creation and to transport this information end-to-end – from the UI to the client to the server and into the persistence.

    From a UI point of view, the existing CreateView is extended with DatePicker and TimePicker, flanked by a checkbox labelled “No expiry”. This combination allows both the explicit determination of an end date and the conscious decision to set an unlimited validity. A small but crucial UX rule: the time will remain disabled until a date is selected, and both fields will be disabled if “No expiry” is enabled.

    Fields and Basic Configuration

    private final TextField urlField = new TextField("Target URL");private final TextField aliasField = new TextField("Alias (optional)");private final Button shortenButton = new Button("Shorten");private final DatePicker expiresDate = new DatePicker("Expires (date)");private final TimePicker expiresTime = new TimePicker("Expires (time)");private final Checkbox noExpiry = new Checkbox("No expiry");private final FormLayout form = new FormLayout();public CreateView() {  setSpacing(true);  setPadding(true);  urlField.setWidthFull();  aliasField.setWidth("300px");  shortenButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);  form.add(urlField, aliasField);  configureExpiryFields();  form.setResponsiveSteps(      new FormLayout.ResponsiveStep("0", 1),      new FormLayout.ResponsiveStep("600px", 2)  );  form.setColspan(urlField, 2);  var actions = new HorizontalLayout(shortenButton);  actions.setAlignItems(Alignment.END);  Binder for validations  Binder<ShortenRequest> binder = new Binder<>(ShortenRequest.class);  ShortenRequest request = new ShortenRequest();  binder.forField(urlField)      .asRequired("URL must not be empty")      .withValidator(url -> url.startsWith("http://") || url.startsWith("https://"), "Only HTTP(S) URLs allowed")      .bind(ShortenRequest::getUrl, ShortenRequest::setUrl);  binder.forField(aliasField)      .withValidator(a -> a == null || a.isBlank() || a.length() <= AliasPolicy.MAX, "Alias is too long (max " + AliasPolicy.MAX + ")")      .withValidator(a -> a == null || a.isBlank() || a.matches(REGEX_ALLOWED), "Only [A-Za-z0-9_-] allowed")      .bind(ShortenRequest::getShortURL, ShortenRequest::setShortURL);  shortenButton.addClickListener(_ -> {    var validated = binder.validate();    if (validated.hasErrors()) return;    if (!validateExpiryInFuture()) return;    if (binder.writeBeanIfValid(request)) {      computeExpiresAt().ifPresent(request::setExpiresAt);      var code = createShortCode(request, computeExpiresAt());      code.ifPresentOrElse(c -> {            Notification.show("Short link created: " + c);            clearForm(binder);          },          () -> Notification.show("Alias already assigned or error saving", 3000, Notification.Position.MIDDLE));    }  });  add(new H2("Create new short link"), form, actions);}

    Encapsulating process logic in small auxiliary methods improves readability and testability. The calculation uses the local time zone and provides an instant that can be processed unchanged on the server side.

    private static final ZoneId ZONE = ZoneId.systemDefault();private void configureExpiryFields() {  expiresDate.setClearButtonVisible(true);  expiresDate.setPlaceholder("dd.MM.yyyy");  expiresTime.setStep(Duration.ofMinutes(1));  expiresTime.setPlaceholder("HH:mm");  Activate time only when a date is set  expiresTime.setEnabled(false);  expiresDate.addValueChangeListener(ev -> {    boolean hasDate = ev.getValue() != null;    expiresTime.setEnabled(hasDate && !noExpiry.getValue());  });  noExpiry.addValueChangeListener(ev -> {    boolean disabled = ev.getValue();    expiresDate.setEnabled(!disabled);    expiresTime.setEnabled(!disabled && expiresDate.getValue() != null);  });  form.add(noExpiry, expiresDate, expiresTime);}private Optional<Instant> computeExpiresAt() {  if (Boolean.TRUE.equals(noExpiry.getValue())) return Optional.empty();  LocalDate d = expiresDate.getValue();  LocalTime t = expiresTime.getValue();  if (d == null || t == null) return Optional.empty();  return Optional.of(ZonedDateTime.of(d, t, ZONE).toInstant());}private boolean validateExpiryInFuture() {  var exp = computeExpiresAt();  if (exp.isPresent() && exp.get().isBefore(Instant.now())) {    Notification.show("Expiry must be in the future");    return false;  }  return true;}private void clearForm(Binder<ShortenRequest> binder) {  urlField.clear();  aliasField.clear();  noExpiry.clear();  expiresDate.clear();  expiresTime.clear();  binder.setBean(new ShortenRequest());  urlField.setInvalid(false);  aliasField.setInvalid(false);}

    It is important to pass it on transparently to the client. Instead of storing the time in the UI, it is transmitted to the server in the request. To do this, the CreateView calls the client with the extended signature. The client validates only if an alias is set and passes the data to the server unchanged.

    private Optional<String> createShortCode(ShortenRequest req, Optional<Instant> expiresAt) {  logger().info("createShortCode with ShortenRequest '{}'", req);  try {    var customMapping = urlShortenerClient.createCustomMapping(req.getShortURL(), req.getUrl(), expiresAt.orElse(null));    return Optional.ofNullable(customMapping.shortCode());  } catch (IllegalArgumentException | IOException e) {    logger().error("Error saving", e);    return Optional.empty();  }}

    On the client side, the payload is serialised as a ShortenRequest and sent to the /shorten endpoint. The response is not limited to the shortcode; it is also parsed as a full ShortUrlMapping. As a result, the UI immediately knows the server-side confirmed state – including the expiresAt.

    public ShortUrlMapping createCustomMapping(String alias, String url, Instant expiredAt) throws IOException {  logger().info("Create custom mapping alias='{}' url='{}' expiredAt='{}'", alias, url, expiredAt);  if (alias != null && !alias.isBlank()) {    var validate = AliasPolicy.validate(alias);    if (!validate.valid()) {      var reason = validate.reason();      throw new IllegalArgumentException(reason.defaultMessage);    }  }  URL shortenUrl = serverBaseAdmin.resolve(PATH_ADMIN_SHORTEN).toURL();  HttpURLConnection connection = (HttpURLConnection) shortenUrl.openConnection();  connection.setRequestMethod("POST");  connection.setDoOutput(true);  connection.setRequestProperty(CONTENT_TYPE, JSON_CONTENT_TYPE);  var shortenRequest = new ShortenRequest(url, alias, expiredAt);  String body = shortenRequest.toJson();  try (OutputStream os = connection.getOutputStream()) {    os.write(body.getBytes(UTF_8));  }  int status = connection.getResponseCode();  if (status == 200 || status == 201) {    try (InputStream is = connection.getInputStream()) {      String jsonResponse = new String(is.readAllBytes(), UTF_8);      ShortUrlMapping shortUrlMapping = fromJson(jsonResponse, ShortUrlMapping.class);      return shortUrlMapping;    }  }  if (status == 409) {    throw new IllegalArgumentException("Alias already in use");  }  throw new IOException("Unexpected status: " + status);}

    On the server side,  the ShortenHandler receives the extended request, validates the required fields, and then assigns the store to create it. The response contains the complete mapping object that the client and UI can use immediately.

    final String body = readBody(ex.getRequestBody());ShortenRequest req = fromJson(body, ShortenRequest.class);if (isNullOrBlank(req.getUrl())) {  writeJson(ex, BAD_REQUEST, "Missing 'url'");  return;}final Result<ShortUrlMapping> urlMappingResult = store.createMapping(req.getShortURL(), req.getUrl(), req.getExpiresAt());urlMappingResult  .ifPresentOrElse(success -> logger().info("mapping created success {}", success.toString()),                   failed -> logger().info("mapping created failed - {}", failed));urlMappingResult  .ifSuccess(mapping -> {    final Headers h = ex.getResponseHeaders();    h.add("Location", "/r/" + mapping.shortCode());    writeJson(ex, fromCode(201), toJson(mapping));  })  .ifFailure(errorJson -> {    try {      var parsed = JsonUtils.parseJson(errorJson);      var errorCode = Integer.parseInt(parsed.get("code"));      var message = parsed.get("message");      writeJson(ex, fromCode(errorCode), message);    } catch (Exception e) {      writeJson(ex, CONFLICT, errorJson);    }  });

    In summary, a consistent, end-to-end process is created: The user optionally defines an expiration date when making them, the UI validates basic rules, the client transfers the semantics losslessly, and the server persists them reliably. The OverviewView and the DetailsDialog can then display and interpret this information immediately. This adds a central property to the domain without complicating the existing operating flow.

    Navigation and package structure

    The previous user interface has become much more complex in terms of content due to the detailed dialogue and the extended form functions. To reflect this development, the package structure in the UI module was clearly reorganised. The goal was not only a logical grouping according to responsibilities, but also a long-term basis for extended navigation concepts and modular extensions.

    Previously, the OverviewView was still in the general package com.svenruppert.urlshortener.ui.vaadin.views. With the introduction of detailed dialogue and the growing importance of this area in terms of content, it was included in a separate subpackage, “views.overview“, and moved there. This decision not only creates room for additional components (e.g. context menus, helper dialogues or filters), but also follows the principle of functional coherence: All classes that together form the overview function are now centrally bundled.

    In the code, this step is reflected in the customisation of the import within the MainLayout :

    old:

    import com.svenruppert.urlshortener.ui.vaadin.views.OverviewView;

    new:

    import com.svenruppert.urlshortener.ui.vaadin.views.overview.OverviewView;

    This small but significant step marks the transition from a purely page-based structure to a component-oriented structure. The MainLayout continues to serve as the central navigation element of the application; the individual views are now more decoupled and can be further developed independently. This creates a clear separation between layout logic (central navigation, menus, visual frameworks) and functional logic (display, interaction, data flow).

    The route relationships are deliberately kept simple. The OverviewView is still registered under the path /overview and uses the MainLayout as its parent layout element:

    @PageTitle Overview@Route(value = OverviewView.PATH, layout = MainLayout.class)public class OverviewView extends VerticalLayout implements HasLogger {    public static final String PATH = "overview";    // ...}

    This configuration keeps navigation consistent with the other parts of the application, such as CreateView, AboutView,  or YoutubeView. New views can be easily added without affecting the central navigation mechanism. This ensures maintainability and scalability – two central requirements for an application that grows gradually within the Advent calendar framework.

    The MainLayout itself is largely unchanged, but has been updated during the package migration to reflect new imports and menu entries. The referencing of the OverviewView is particularly important, as it represents the entry point of user interaction:

    SideNavItem overview = new SideNavItem("Overview", OverviewView.class, VaadinIcon.LIST.create());SideNavItem create = new SideNavItem("Create", CreateView.class, VaadinIcon.PLUS.create());SideNavItem about = new SideNavItem("About", AboutView.class, VaadinIcon.INFO_CIRCLE.create());SideNavItem youtube = new SideNavItem("Youtube", YoutubeView.class, VaadinIcon.YOUTUBE.create());SideNav nav = new SideNav(overview, create, about, youtube);addToDrawer(nav);

    This clean separation between routing, structure and presentation lays the foundation for the further development of the user interface. Future features – such as detailed filters, user preferences or administrative functions – can be easily integrated into their own subpackages and namespaces without affecting core navigation. This is the step towards a sustainable UI architecture that specifically supports both growing complexity and extensibility.

    Interaction patterns and UX coherence

    As the application’s feature density increases, the importance of a consistent user experience grows. While the first days of the Advent calendar laid the technical foundation, so far, the focus has been on functionality. In this chapter, the focus shifts to the coherence of interaction patterns, i.e. how users interact with the application in a consistent, predictable rhythm.

    Central to this is the goal of establishing uniform behaviour for recurring actions. Whether in the overview, in the detail dialogue or in forms – copying, opening or deleting should always feel the same. The application thus conveys reliability, which is particularly crucial for technical users of web-based tools.

    Uniform copying behaviour

    A good example is copying URLs and short links. The exact mechanisms are used in both the grid and the detail dialogue: a button with the VaadinIcon.COPY symbol: an asynchronous clipboard action via JavaScript and a discreet confirmation message. This avoids users having to learn different forms of interaction in various views.

    copy.addClickListener(_ -> {  UI.getCurrent().getPage().executeJs("navigator.clipboard.writeText($0)", SHORTCODE_BASE_URL + m.shortCode());  Notification.show("Shortcode copied");});

    Such a detail may seem inconspicuous, but it has a significant impact on the perceived professionalism of the application. The feedback system via notifications plays a central role here: The user receives an immediate, unobtrusive signal about the success of his action – a kind of visual receipt that creates trust.

    Context menus and multiple interactions

    Another element of UX coherence is the context menu in the summary table. It allows access to the same actions, which can also be accessed via buttons or double-clicks. This redundant but deliberate multiple interaction follows the principle of user freedom: Users can choose whether to act via direct icons, the keyboard or the context menu.

    GridContextMenu<ShortUrlMapping> menu = new GridContextMenu<>(grid);menu.addItem("Show details", e -> e.getItem().ifPresent(this::openDetailsDialog));menu.addItem("Open URL", e -> e.getItem().ifPresent(m -> UI.getCurrent().getPage().open(m.originalUrl(), "_blank")));menu.addItem("Copy shortcode", e -> e.getItem().ifPresent(m -> UI.getCurrent().getPage().executeJs("navigator.clipboard.writeText($0)", m.shortCode())));menu.addItem("Delete...", e -> e.getItem().ifPresent(m -> confirmDelete(m.shortCode())));

    The decision to use context menus is not only aesthetic but also ergonomic: it reduces the grid’s optical density without sacrificing functionality. Actions only appear when they are needed – an approach that is essential in complex management interfaces.

    Consistency of feedback

    A uniform pattern is also followed for error messages and validations. The system does not abruptly abort user actions; instead, it clearly communicates why an input was not accepted. Example: The expiration date cannot be in the past. This rule is conveyed both visually and by message.

    if (exp.isPresent() && exp.get().isBefore(Instant.now())) {  Notification.show("Expiry must be in the future");  return false;}

    Accurate feedback prevents the user from perceiving the system as unpredictable. Every validation, every hint, and every success message follows the same communicative style—short, clear, and polite.

    HTTPS Requirement for Clipboard

    A technical but essential detail is that the Clipboard API only works in modern browsers within secure contexts (HTTPS or localhost). Therefore, the copy feature is deliberately designed to fail elegantly when clipboard access is unavailable. The application does not crash, but responds silently – an aspect that underlines the robustness and professionalism of the user experience.

    The mechanisms described in this chapter – consistent feedback, redundant operating options and secure fallbacks – together contribute to a coherent user experience. They form the basis of trust and predictability, two characteristics that are essential as software becomes more complex. Overall, the result is an interface that can be operated intuitively, even as its technical depth in the background continues to increase.

    Cheers Sven

    #Advent2025 #EclipseStore #Java #Vaadin

  24. Good morning! Here are today’s #Advent2025 readings if you’re following along:

    Psalm 72:1-7, 18-19; Isaiah 4:2-6; Acts 1:12-17, 21-26