Skip to content

1.10.0

Latest

Choose a tag to compare

@gbevin gbevin released this 10 Sep 06:10
· 14 commits to main since this release

This release adds server-sent events, htmx support, CSRF protection, database migrations and JSON, and improves GraalVM native image support.

Existing applications should read the compatibility notes at the end.

Web engine

  • Added server-sent events with broadcasting, history replay, htmx and JSON integration, bridged to the generic query manager and the workflow engine.
  • Added SseConnectionFilter to select the connections that SseBroadcaster.send and close act on, and SseErrorListener to report event conversion and broadcasting failures, which is what SseGqmBridge.onError registers.
  • Added htmx support: request accessors, response headers with trigger accumulation, location context and history suppression, and Vary handling through Context.varyOn.
  • Added Context.printBlock to write a single template block, and Context.printHtmxFragment to serve either the full page or an htmx fragment, including the history restoration guard and the matching Vary headers.
  • Added opt-in CSRF protection with SameSite cookie support and configurable cookie, parameter and header names.
  • Added the context:csrfToken and context:htmxHeaders template values, and route:inputs: tags now add a hidden CSRF parameter when a token is active for the request.
  • Added the SameSite enum and CookieBuilder.sameSite, so any cookie an application sets can carry a SameSite policy, and AuthConfig.cookieSameSite to set it for the authentication and remember-me cookies.
  • Added Router.destroy(), which is called for every grouped router when a site shuts down. Prior to 1.10 this method only existed on Site and only ran for the top-level site.
  • Added Server.connectionIdleTimeout and TomcatServer.connectionTimeout, and the embedded servers mark their RIFE2 filter and default servlet as async supported, so that long-lived event streams stay open. A war deployment that uses detached SSE connections has to add <async-supported>true</async-supported> to the RIFE2 filter declaration in web.xml, and to every other filter and the servlet in the request's chain.
  • Added bean population restricted to a validation group or to an explicit collection of properties. A selected property whose parameter is absent is reset to the value of a new bean instance rather than left alone, and a boolean becomes false, since browsers don't send unchecked checkboxes. A file property keeps its content when no upload arrived.
  • Added a paged navigation variant that carries extra parameters in every link.
  • Fixed #58: multipart requests failing under Jakarta EE 11.
  • Fixed @Config list injection to resolve the generic element type against the concrete element class, so an inherited List<T> field receives converted values instead of raw strings.

Database

  • Added declarative database migrations, with reversible migrations through ReversibleDbMigration and preview and previewFrom counterparts to migrate and migrateFrom. The resource that holds the migration state is configurable through RifeConfig.migrations(), and the rollback methods return the version the schema actually reached.
  • Added the AlterTable, CreateIndex, DropIndex, CreateView, DropView and Truncate query builders, bean metadata support in the DDL builders and multiple drops.
  • Added the columnName constraint to map a bean property to a differently named column (rife2/rife2-core#4).
  • Added the file constraint for properties that receive their content through uploads.
  • Added parameterized where clauses to the generic query manager restore and count queries.
  • Added DbConnection.getTransactionIsolation. Transactions with a custom isolation level restore the previous level when they complete, and nested transaction users inherit the enclosing isolation.
  • Transactions now complete consistently for every exception and error that ends them, so the control flow that redirect and respond are built on commits instead of rolling back.
  • Connections are released on every path when the datasource has no transactions.
  • Fixed generic query manager relationship element detection to resolve an inherited generic type argument against the concrete bean class, where the type variable previously caused a ClassCastException.

Beans, validation and forms

  • Added transparent support for Optional bean properties, which are unwrapped wherever a property is read or written (#54, rife2/rife2-core#7).
  • Added JSON parsing, generation and bean conversion. Json.toString converts beans and records automatically, and JSON templates now use the dedicated BeanHandlerJson, which honors the new serialized constraint.
  • Added type variable resolution to ClassUtils through resolveTypeVariable and erasedType.
  • Added BeanUtils.parsesWithFormat, formatPropertyValueForInput and parseInputValue.
  • Form generation writes each field value the way that field reads it back, including a constraint's defaultValue.
  • Text read through a constraint's format must now be consumed whole and must fit the property.
  • Cloning a constrained property copies the constraint values that can be cloned, instead of sharing all of them.

Continuations and native images

  • Reworked continuation instrumentation around stack-map frames.
  • Added ahead-of-time instrumentation through InstrumentationDeployer as an alternative to the java agent.
  • Improved GraalVM native image support with build-time reflection config generation. CI verifies that the resources RIFE2 ships stay reachable both on the module path and in a native image.
  • Continuation failures are no longer treated as control flow exceptions.
  • Fixed metadata instrumentation for wide arguments and parameterized constructors, and fixed the lazy-load accessor frame computation to use the right class loader.
  • Updated the bundled ASM, which reads class files up to Java 27.

Workflow

  • Added error handling with error listeners to the workflow engine. Work that ends with an exception is now reported instead of being swallowed, and no longer leaves the workflow waiting forever.
  • Workflow.waitForNoWork now loops until all work has finished, and waitForPausedWork returns whether it stopped on paused work.
  • Event listeners are notified before paused work resumes, so a listener receives the triggering event before any event that the resumed work triggers.
  • Fixed races that could strand a pending event, or report completion before resumed work had finished.
  • The workflow engine moved from an alpha experimental stage to a beta stage.

Out-of-container testing

  • MockConversation implements AutoCloseable and exposes destroy, so that the teardown lifecycle of a site can be tested out of container.
  • Added MockRequest.htmx, MockResponse.getEvents and MockEvent for asserting on htmx requests and event streams.
  • Mock cookies copy their attributes, so SameSite survives a round trip.

Other

  • Added DirectoryResources for writing resources as files in a directory.
  • Added rife.tools.Product, and downloads now identify themselves with a standard User-Agent, contributed by Erik Thauvin.
  • Fixed #57: incorrect HTML encoding for bean values with Unicode. HTML encoding and decoding handle surrogate code points, and numeric entities with invalid code points are left as-is.
  • Added support for Java 26. RIFE2 runs on Java 17 and later, and is tested on Java 17, 21, 25 and 26.
  • Added a snapshot publishing workflow, so that snapshot builds are available between releases.
  • Improvements to tests and CI.
  • Updated to latest bld, which is now 3.0.0.
  • Updated to latest dependencies. The embedded servers moved to Jetty 12.1.12 and Tomcat 11.0.25.

Compatibility notes

Authentication

  • The authentication and remember-me cookies now carry SameSite=Lax by default, where they previously carried no SameSite attribute at all. An application that needs these cookies delivered cross-site can set AuthConfig.cookieSameSite(SameSite.NONE), but browsers only accept that when the cookie is Secure as well, and RIFE2 sets Secure from the scheme of the request, so the request has to be recognized as HTTPS.

Transactions and control flow

  • Transaction completion changed. An exception that implements ControlFlowRuntimeException now commits the enclosing transaction whether it is a RuntimeException or an Error. Previously only the RuntimeException ones committed and every Error rolled back, which is what the web engine's own control flow is built on, since redirect, respond, defer and next throw subclasses of LightweightError. A LightweightError that does not implement ControlFlowRuntimeException still rolls back. A commit that fails now replaces the exception that ended the transaction and keeps the original one as a suppressed exception.
  • The continuation failure exceptions ContinuationsNotActiveException, CallTargetNotFoundException, MissingActiveContinuationConfigRuntimeException and NoContinuableInstanceAvailableException no longer implement ControlFlowRuntimeException. An element transaction that ends with one of them now rolls back where it previously committed, and a catch block inside a continuable method now sees them where instrumentation previously rethrew them past it. The continuation signals PauseException, CallException, AnswerException and StepBackException are unchanged.

Database

  • GenericQueryManager gained five methods that take a DbPreparedStatementHandler, so a class that implements the interface directly has to implement those as well.

Bean population and conversion

  • Optional bean properties are now unwrapped transparently, so an application that already had an Optional getter renders and persists the contained value where it previously wrote Optional[value].
  • Populating a bean from parameters now falls back to Convert.toType for property types it has no specific branch for. Properties that were previously left unset are now filled in, and a value that will not convert records a validation error, so a form that used to validate can now fail.
  • Text read through a constraint's format must be consumed whole and must fit the property. A whole number property that used to truncate a fraction, wrap an out of range value around, or keep only the part of the text the format could read now refuses the value: a Validated bean records a validation error, and a plain bean throws a BeanUtilsException. A char or Character property that used to keep the first character of longer text now refuses it too, recording a validation error on a Validated bean and leaving the property unset otherwise.
  • java.sql.Time properties are formatted with the time format, so their default rendering changes from yyyyMMddHHmmssSSSZ to HHmmssSSSZ. The java.sql date and time types are also recognized before java.util.Date when populating a bean, where they previously landed in the Date branch and were rejected.
  • Bean property names are uppercased with Locale.ROOT, so case-insensitive matching of parameter names to bean properties no longer depends on the JVM's default locale.
  • editable(false) now also stops uploaded files from being assigned, matching what it already did for regular parameter values.
  • Convert.toDate, toInstant, toLocalDate, toLocalDateTime and toLocalTime fall back to the JDK's ISO-8601 parser when none of the configured formats match, instead of throwing a ConversionException. toDate and toInstant need the full ISO instant form with a zone offset. The java.sql conversions are unchanged.

Forms and validation

  • Generated form field values, including a constraint's defaultValue, are written the way the field submits them back. A constraint's format is no longer applied to String, char and Character, boolean and Boolean, StringBuffer, StringBuilder, enums, or arrays of those. A default now only stands in when the property holds no value at all, rather than when its first value is missing.
  • ConstrainedProperty.clone() copies constraint values through ObjectUtils.deepClone, so formats, inList arrays and the copyable associations are no longer shared with the original and mutating them through a clone no longer reaches it. An immutable value such as a String default value is shared rather than copied, and a custom object is copied by its own clone() implementation.
  • ValidityChecks.checkNotEmpty accepts any CharSequence instead of throwing a ClassCastException on a StringBuilder or StringBuffer, and checkInList ignores nulls both in the allowed values and in the value being checked, instead of throwing a NullPointerException.
  • Validation.hasPropertyConstraint activates lazy validation like the other accessors do. Relational handling in the generic query manager could previously be skipped on a subclass that registers its constraints in activateValidation().

Web engine

  • Site.destroy() moved to Router.destroy(), so a destroy() on a grouped router that never ran before now runs. Routers are also destroyed before the schedulers stop and the datasources close, which is the reverse of the previous order.
  • route:inputs: tags emit an extra hidden CSRF input when a token is active for the request, so tests that compare generated HTML exactly will need updating.
  • Responses with content type text/event-stream are never gzip compressed, regardless of RifeConfig.engine().getGzipCompressionTypes().
  • Multipart requests take their query string parameters from RIFE2 rather than from the container, decoded as UTF-8. On a container configured with a different URI encoding, those parameters decode differently.
  • HTML encoding and decoding treat a surrogate pair as one code point, so generated HTML changes for content outside the basic multilingual plane.

Workflow

  • Workflow.waitForPausedWork returns boolean instead of void, true when work is paused and false when no active work remains. Existing callers can keep ignoring the result but have to be recompiled, and a subclass that overrides the method has to change its return type.
  • Event listeners are notified before the work they awaken resumes, which is the reverse of the previous order.

Out-of-container testing

  • Mock cookies without a path are stored under / in MockConversation, so they no longer sit beside a cookie that specifies /.