This document contains upgrade notes for Agrest 1.x - 4.x. Newer versions are documented in UPGRADE-NOTES.
Bulk updates may return "201" status where previously they returned "200" #503
Now that we have more precise data about operation types on update, Agrest would return status 201 instead of 200 for bulk create operations (previously it would only return 201 for single-object create ops). Client code must be aware of this and act accordingly. A mix of create and update operations would still return 200, as before.
Bulk deletes may require custom code #505
Previously Agrest allowed for a special case of a "full table delete" to be processed in the database without bringing
the objects to the app memory (via delete from xyz_table
). From now on "full table delete" is processed the same way
as other delete cases - by selecting and analyzing the objects first. If your code uses "full table delete" on very
large tables, this operation may become noticeably slower and may even fail due to insufficient memory. If you need
such deletes (and are willing to ignore the authorizers), you may bypass Agrest, and delete via Cayenne directly.
Alternatively you can use the new stage API per #506 to implement
custom processing within the Agrest pipeline.
A number of new stages were added to "select" and "update" pipelines. If you have custom stages in your code, you may need to understand the implications:
-
Select pipeline now has
SelectStage.FILTER_RESULT
andSelectStage.ENCODE
added afterSelectStage.FETCH_DATA
, that was previously the last stage of the pipeline. Response Encoder is not available until the "ENCODE" stage. -
Update pipeline has
UpdateStage.FILTER_RESULT
andUpdateStage.ENCODE
added afterUpdateStage.FILL_RESPONSE
, that was previously the last stage of the pipeline. Response Encoder is not available until the "ENCODE" stage. This may actually break a common extension pattern used previously - the use ofterminalStage
to provide the response data via a custom method, and skip the actual processing:
Ag
.select(E1.class, config)
.terminalStage(SelectStage.APPLY_SERVER_PARAMS, this::provideData)
.get();
This will no longer work, resulting in an NPE for the encoder during encoding. The new way of handling this case is to provide a custom "root data resolver" via entity overlay:
Ag
.select(E1.class, config)
.entityOverlay(AgEntity.overlay(E1.class).redefineDataResolver(this::provideData))
.get();
- Update pipeline has
UpdateStage.MAP_CHANGES
andUpdateStage.AUTHORIZE_CHANGES
added betweenUpdateStage.APPLY_SERVER_PARAMS
andUpdateStage.MERGE_CHANGES
. "MAP_CHANGES" was split from "MERGE_CHANGES", as it is a separate logical step. It gives the new "AUTHORIZE_CHANGES" stage an opportunity to check which objects are allowed to be updated.
@ClientReadable
and @ClientWritable
annotations are removed in favor of per-attribute access controls #491
As a part of an effort to unify constraint handling, @ClientReadable
and @ClientWritable
annotations have been
removed, as a rather inelegant mechanism to reference class properties. Instead @Ag*
annotations on getters
(@AgId
, @AgAttribute
and @AgRelationship
) now provide "readable" and "writable" properties. By default all
properties are both readable and writable. So you must review your code and replace @ClientReadable
and
@ClientWritable
with explicit annotations on properties that you would like to exclude from either reading or
writing. E.g. if a property "a" was "client-readable", then @ClientReadable
annotation should be removed, and
properties "b", "c", etc. should be annotated with @AgAttribute(readable=false)
/ @AgRelationship(readable=false)
Injectable EntityConstraint is removed in favor of per-attribute access controls #491
There was a little-known mechanism to programmatically define runtime-wide policy for entity read and write property
exclusion - an injectable EntityConstraint
. List injection keys were agrest.constraints.read.list
and agrest.constraints.write.list
for read and write constraints respectively. As a part of an effort to unify constrain handling, this mechanism was
removed. There are two alternatives: @Ag*
annotations on attributes, relationships and ids now have "readable" and
"writable" properties; also AgEntityOverlay
API allows redefining existing property readability and writeability,
either globally or per-request.
Updates may return "201" status where previously they returned "200" #490
All variants of "create or update" operations would now return 201 code instead of 200 when performed on a single object and if the object was newly created. While this is the correct response code (and the previous behavior was incorrect), this may affect existing assumptions made by Agrest consumers. Consumer code should be revisited to ensure that 201 response code is properly handled.
AgException API changed #495
AgException
API has changed. javax.ws.rs.core.Response.Status
was replaced with an int inside AgException
.
So any code explicitly creating this exception, must pass standard HTTP status codes as integers. For convenience, they
are defined in io.agrest.HttpStatus
.
Instead of public constructors, AgException now provides a set of static factory methods that allow to build exception messages with String templates and parameters.
'Exp' constructors replaced with static factory methods #476
Exp
class was converted to an interface to allow to compose expression trees from parts. As a result all its
constructors got replaced with convenience static methods - Exp.simple()
, Exp.withPositionalParams()
,
Exp.withNamedParams()
. If you see compilation errors, replace constructor calls accordingly.
Legacy "io.agrest:agrest" module is removed #472
If you imported legacy io.agrest:agrest
module as a dependency, remove it, and import these two modules instead:
io.agrest:agrest-engine
and io.agrest:agrest-cayenne
(of course Cayenne module is needed only if you are using
Cayenne as Agrest backend).
Metadata API is deprecated in favor of OpenAPI #466
Since we now support integration with OpenAPI / Swagger, Agrest own less capable metadata API is deprecated. While we plan to keep it around for a while, it is wise to stay awy from it, and if you are already using it, switch to OpenAPI.
This release is integrated with Cayenne 4.2 and will not work with earlier Cayenne releases.
"SelectBuilder.property()" replaced with simpler "SelectBuilder.entityAttribute()" #452
As a part of the effort to consolidate various customization APIs, two "SelectBuilder.property()" methods were replaced
with a single "SelectBuilder.entityAttribute()" method that relies on the standard entity overlay customization
mechanism behind the scenes. It is more user-friendly, as it allows a user to provide Function<T, V> reader
instead of an obscure EntityProperty property
.
"ResourceEntity.getSelect()" is not available #453
ResourceEntity.getSelect()
is removed, so that there's no direct Cayenne dependency in generic Agrest API.
While there's still a way to access it using CayenneProcessor
class, you will no longer be able to set an arbitrary
query as a "template" for Cayenne. So if you need to customize query parameters, such as expressions, orderings, includes,
look into using ResourceEntity API instead.
Cayenne Expression is no longer part of ResourceEntity #457
In a continuing effort to remove dependency on Cayenne, ResourceEntity tree is no longer using Expression "qualifier". Instead ir is replaced with a collection of Agrest CayenneExp
objects. This may effect customization code if it attempted to change query conditions. Our recommendation is to replace Cayenne Expression class in any such code with CayenneExp.
A special case is Sencha module. Callers of SenchaOps.startsWithFilter(..)
must attach it to SelectStage.APPLY_SERVER_PARAMS
stage, not SelectStage.ASSEMBLE_QUERY
, or it will not be applied.
Separate "commit" in its own UpdateStage #446
If you customized UpdateStage.UPDATE_DATA_STORE
stage on update, change the stage name from UPDATE_DATA_STORE
to COMMIT
. It should produce the same result.
Isolating a pluggable Cayenne backend #433
Code related to Cayenne backend was isolated in its own module agrest-cayenne
. The old agrest
module
depends on it, so dependencies do not need to be upgraded. However bootstrapping Cayenne is now done differently.
ServerRuntime
is not longer passed to AgBuilder
. You must pass it to AgCayenneBuilder
, and use it to
create a module that is passed to AgBuilder
:
ServerRuntime runtime = ..;
AgCayenneModule cayenneExt = AgCayenneBuilder.build(runtime);
AgBuilder agBuilder = new AgBuilder().module(cayenneExt);
Also classes in io.agrest.protocol
where moved to io.agrest.base.protocol
package. Change your imports
accordingly.
Support for Case Insensitive Sorting #428
Support was added for case-insensitive sorting to the Agrest protocol and the backend framework. While this change does not require any upgrade actions and is fully backwards-compatible, since this is a rare protocol addition, it is worth mentioning it here.
EncoderFilter API Changes #420
EncoderFilter
was renamed toEntityEncoderFilter
, as the new name points to the exact place where filtering occurs (per entity object).- Previous
EntityEncoderFilter
that was used as an abstract superclass for the filters was removed. If you have code that subclasses it, switch toEntityEncoderFilter
static builder methods instead:
var x = ..
EntityEncoderFilter filter = EntityEncoderFilter.forEntity(E4.class)
.encoder((p, o, out, e) -> {
out.writeStartObject();
out.writeObjectField("x", x);
out.writeEndObject();
return true;
})
.build();
Pluggable Resolvers make MultiSelectBuilder
obsolete #413
This feature allows to install custom resolvers for root and nested entities in an Ag request, either globally or per-request.
This new capability allows to fetch data from multiple sources, aggregating it in a single response on the fly (all without
altering model objects for each source). This is very powerful, and it makes our earlier experiment with AgMultiSource
and MultiSelectBuilder
obsolete. These two classes are deprecated. If you were using them, consider switching to
custom resolvers and AgEntityOverlay
.
Response with overlapping relationship / attribute "includes" is include order dependent #406
If you are using combinations of relationship and attribute includes (e.g. include=e2&include=e2.id
), you may have received
more fields in response than you should, depending on the include order. And your code may have relied on such an incorrect
behavior. If suddenly now you are getting less data than you expected for a given request, you will need to adjust client-side
includes to match the data that you need.
@QueryParameter types changed to Strings and Integers #408
- Inspect your
@QueryParam
injection code and change Agrest types, such asInclude
,Exclude
, etc. to Strings. ChangeStart
andLimit
toIntegers
. - Replace calls to
AgRequest.builder()
withAg.request(configuration)
.
If you are not directly injecting Include
, Exclude
and friends (injecting UriInfo
instead), are you are not affected.
This is the release in which "LinkRest" got renamed to "AgRest", causing the renaming of modules, packages , and class prefixes. When upgrading please change the following:
- Change dependency imports from
com.nhl.link.rest:link-rest-*
toio.agrest:agrest-*
- Change package imports from
com.nhl.link.rest.*
toio.agrest.*
. - Update your class references that start with
Lr
toAg
prefix.
LinkRestAdapter is removed #340
The adapter was deprecated for a while in favor of AgFeatureProvider
and AgModuleProvider
. Now it is
finally removed.
"link-rest-legacy-date-encoders" module is removed #340
We no longer support legacy date/time encoders. See upgrade instructions for version 2.11 for details on how to update your code.
Removed listeners and listener annotations #300
As a part of the effort cleaning up deprecated API, support for stage listeners and stage listener annotations was removed. If you need to extend LinkRest processing chains, you should be using "stage" and "terminalStage" methods with custom lambdas. Those are more flexible and easy to understand. See 2.7 upgrade notes below in this document for hints on how to use stages.
"query" protocol parameter was moved to "link-rest-sencha" #301
Support for "query" protocol parameter (doing case insensitive "starts with" search on a server-specified property)
is now limited to the Sencha flavor of LinkRest. Base LinkRest no longer supports this operation. If you are using link-rest-sencha
,
replace calls to SelectBuilder.autocompleteOn
with calls to .stage(SelectStage.PARSE_REQUEST, SenchaOps.startsWithFilter(T.NAME, uriInfo))
.
Either SelectStage.PARSE_REQUEST or SelectStage.ASSEMBLE_QUERY stages can be used.
If you are not using the Sencha module, you can inspect SenchaOps
code implement a similar function on your own.
PARSE_REQUEST stage got split in two #309
If you have callbacks attached to SelectStage.PARSE_REQUEST
or UpdateStage.PARSE_REQUEST
stages that rely on the presence of ResourceEntity
in the context, reattach them to SelectStage.CREATE_ENTITY
or UpdateStage.CREATE_ENTITY
respectively, as ResourceEntity
only becomes available after that new stage.
JSON encoders stop rendering timezone for all date/time values #275
Encoding of local date/time values is now uniform for all attribute types and is based on the following rules:
- everything is formatted in server's default TZ
- TZ is never specified in the formatted string
- time is not truncated to seconds, so a fractional part may appear in the formatted string
- fractional part is truncated to milliseconds during encoding
To revert these changes and go back to the old behavior you may use com.nhl.link.rest.LegacyDateEncodersModule
. In case
the modules auto-loading feature is not disabled, it should be sufficient to add the
com.nhl.link.rest:link-rest-legacy-date-encoders
JAR on your application's classpath. Here's how to do it,
if you're using Maven build:
<dependency>
<groupId>com.nhl.link.rest</groupId>
<artifactId>link-rest-legacy-date-encoders</artifactId>
<version>2.11</version>
</dependency>
LinkRestAdapter is deprecated, replaced with LrFeatureProvider and LrModuleProvider #245
Instead of a monolithic adapter, LinkRest new extension mechanism is based on two separate interfaces, LrFeatureProvider
and LrModuleProvider
,
one to provide JAX-RS extensions, the other - for the LinkRest stack extensions. LinkRestAdapter
got deprecated. Consider upgrading to the new
mechanism. Also note that both new providers can be configured for auto-loading using standard Java ServiceLoader mechanism .See
#245 for details.
Renamed @Resource to @LrResource #261
@Resource
annotation was used for annotating REST endpoints to build informative metadata resources. For naming
consistency across LR annotations it was renamed to @LrResource
and placed in link-rest-annotations
module.
While the old form is still operational, it is deprecated and will eventually be removed. When replacing it
with the new form (@LrResource
) you will get an error about one of its attributes, namely the LinkType
enum. @LrResource
uses LinkType from a different package. So be sure to replace com.nhl.link.rest.meta.LinkType
import with
com.nhl.link.rest.annotation.LinkType
.
SenchaAdapter made auto-loadable #263
If you are using LinkRest with Sencha/ExtJS, chances are you've activated SenchaAdapter
. In 2.10 SenchaAdapter
class was removed and replaced with auto-loadable SenchaFeatureProvider
and SenchaModuleProvider
pair. So you will see compilation errors. When you do, remove the adapter registration code and instead add a dependency on the new com.nhl.link.rest:link-rest-sencha
module. After that your app should automatically find and install all Sencha-specific services.
To simplify writing LR extensions, an API alternative to annotated listeners was implemented for "select" and "update" operations. Now you can register specific functions to be invoked after a given stage. E.g.:
LinkRest.select(E.class, config)
.uri(uri)
.stage(SelectStage.PARSE_REQUEST, c -> c.getEntity().setQualifier(E.NAME.eq("John")))
.get();
LinkRest.select(E.class, config)
.uri(uri)
.terminalStage(SelectStage.APPLY_SERVER_PARAMS, object::customBackend)
.get();
Old-style listeners are still supported, but deprecated. While most existing listeners will work unchanged, there are may be issues if the listeners do anything fancy with stage routing (beyond just returning unchanged "next" or null). generally we would recommend to inspect the deprecation warnings and migrate to the new function-centric API.
Immutable Constraints API #214
ConstraintsBuilder
static factory methods were moved (with deprecation) to Constraint
interface. It is advisable to
clean up all deprecation warnings.
The only subltly breaking change is that ConstraintsBuilder
was made immutable, so whenever you are calling one of
its builder methods, a new instance is created. So make sure you do not keep references to the intermediate builder
results (unless this is intentional), and only use the ConstraintsBuilder instance returned from the last builder method.
E.g. the following likely won't do what you'd expect:
ConstraintsBuilder<E> c = Constraint.excludeAll();
c.attribute("a");
// Likely a bug!! "c" does not include attribute "a"
LinkRest.select(MyType.class, config).constraint(c);
while this will:
ConstraintsBuilder<E> c = Constraint.excludeAll().attribute("a");
LinkRest.select(MyType.class, config).constraint(c);
New Feature: support for Java 8 Dates #154
If you want to start using Java 8 date/time types, you will need to import an extra module:
<dependency>
<artifactId>link-rest-java8</artifactId>
<groupId>com.nhl.link.rest</groupId>
<version>1.23</version>
</dependency>
and then add a com.nhl.link.rest.runtime.adapter.java8.Java8Adapter
to LinkRest runtime.
Collection Document: remove "success":true key, keep it under SenchaAdapter #114
LinkRest no longer includes "success":true
key in Collection Document responses, unless you are using SenchaAdapter. The hope is that nobody actually relied on the JSON body to check the status of response, and instead used HTTP status code for that purpose. "success":true
is still available when SenchaAdapter is in use.
UpdateBuilder / UpdateResponse / EntityUpdate refactoring #113
An update chain can return either a Simple Document or a full Collection Document. Before 1.19 this distinction wasn't clear from the API (UpdateBuilder.process()
always retruned UpdateResponse). This is made more explicit in 1.19. Instead of the 'process(String)' method (that was deprecated), there are 2 new methods:
DataResponse<T> syncAndSelect(String);
SimpleResponse sync(String)
Additionally UpdateBuilder.includeData()/excludeData()
methods were removed, as they make no sense any longer. The users must review compilation errors and deprecation warnings and replace those 3 methods with calls to syncAndSelect()
or sync()
. Note that deprecated 'process(String)' method would work the same way as 'sync' with default adapter, and same way as 'syncAndSelect' with SenchaAdapter.
This change also forced us to kill UpdateFilter
. UpdateFilter users must change to annotated listeners available per #111.
Externalizing Processor Chain invocation #112
Implementors of custom processing stages will need to update their API, or better, replace their custom stages with listeners per #111.