-
Notifications
You must be signed in to change notification settings - Fork 0
Hello world #3237
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: zsv_4.10.25
Are you sure you want to change the base?
Hello world #3237
Conversation
Walkthrough新增HelloWorld插件系统,包含API消息和事件类、greeting资源的数据模型、库存类、服务管理器实现及API拦截器,支持问候资源的创建、删除和查询操作。 Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant ApiInterceptor as API拦截器
participant Manager as HelloWorldManagerImpl
participant Database as 数据库
participant Bus as 消息总线
Client->>ApiInterceptor: APIHelloWorldMsg
ApiInterceptor->>ApiInterceptor: validate(greeting非空)
ApiInterceptor->>Manager: handleMessage()
Manager->>Manager: handleAPIMessage()
Manager->>Manager: handle(APIHelloWorldMsg)
Manager->>Bus: sendReplyMessage(APIHelloWorldEvent)
Bus->>Client: APIHelloWorldEvent
sequenceDiagram
participant Client
participant Manager as HelloWorldManagerImpl
participant Database as 数据库
participant GreetingBase as GreetingBase
participant Bus as 消息总线
Client->>Manager: APICreateGreetingMsg
Manager->>Manager: handleAPIMessage()
Manager->>Manager: handle(APICreateGreetingMsg)
Manager->>Database: updateAndRefresh(GreetingVO)
Database->>Manager: GreetingVO
Manager->>Bus: sendReplyMessage(APICreateGreetingEvent)
Bus->>Client: APICreateGreetingEvent(inventory)
Client->>Manager: APIDeleteGreetingMsg
Manager->>Manager: passThrough(GreetingMessage)
Manager->>Database: findByUuid(uuid)
Database->>Manager: GreetingVO
Manager->>GreetingBase: new GreetingBase(vo)
GreetingBase->>GreetingBase: handleMessage(APIDeleteGreetingMsg)
GreetingBase->>Database: remove(self)
GreetingBase->>Bus: sendReplyMessage(APIDeleteGreetingEvent)
Bus->>Client: APIDeleteGreetingEvent
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
✨ Finishing touches
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🤖 Fix all issues with AI agents
In
`@plugin/helloworld/src/main/java/org/zstack/plugin/example/APICreateGreetingMsg.java`:
- Around line 9-20: The APICreateGreetingMsg class lacks the required static
__example__ method used for API doc generation; add a public static
APICreateGreetingMsg __example__() that constructs and returns an instance with
a representative greeting (e.g., "hello world") set via setGreeting, so
documentation and templates can be generated from this example; implement the
method inside the APICreateGreetingMsg class.
In
`@plugin/helloworld/src/main/java/org/zstack/plugin/example/APIDeleteGreetingMsg.java`:
- Around line 8-22: Add a static __example__ method to APIDeleteGreetingMsg that
returns a populated example instance (following other API classes' pattern) so
the API docs can be generated; implement it in the APIDeleteGreetingMsg class
(alongside getGreetingUuid) to construct a new APIDeleteGreetingMsg, set a
sample uuid (use the same sample/uuid helper or a realistic placeholder used in
other examples), and return that instance.
- Around line 3-16: APIDeleteGreetingMsg 中的 uuid
缺少资源校验注解,导致删除接口无法触发资源类型与账号权限校验;在 APIDeleteGreetingMsg 类的 uuid 字段上添加
`@APIParam`(resourceType = GreetingVO.class, checkAccount = true)(或项目中对应的资源 VO
类型)以启用资源类型校验和账号检查,并确保 getter/setter 保持不变以避免序列化问题;确认导入对应的 `@APIParam` 注解和资源 VO
类(引用类名:APIDeleteGreetingMsg、uuid、GreetingMessage、APIDeleteMessage、GreetingVO)。
In
`@plugin/helloworld/src/main/java/org/zstack/plugin/example/APIHelloWorldMsg.java`:
- Around line 9-19: Add a static example() factory to APIHelloWorldMsg so API
docs/templates are generated: implement a public static APIHelloWorldMsg
example() method that creates a new APIHelloWorldMsg instance, sets a
representative greeting (e.g., "hello world" or similar) via setGreeting, and
returns the instance; place this method inside the APIHelloWorldMsg class to
satisfy the documentation generator expectations.
- Around line 8-9: The REST mapping for APIHelloWorldMsg conflicts with
APICreateGreetingMsg because both use POST "/helloworld/greetings"; update
APIHelloWorldMsg's `@RestRequest` annotation to a unique path or HTTP method
(e.g., change path to "/helloworld/hello" or use GET) so routes are unambiguous,
then adjust the APIHelloWorldMsg class annotation and any related
client/tests/docs that reference the old path to match the new endpoint; ensure
you modify the `@RestRequest` on APIHelloWorldMsg and any routing/tests that
assert POST "/helloworld/greetings".
In `@plugin/helloworld/src/main/java/org/zstack/plugin/example/Greeting.java`:
- Around line 5-6: 接口方法 Greeting.handleMessage 缺少 Javadoc;请为该接口方法补充规范的 Javadoc
注释,描述方法的用途、参数(Message msg 的语义和可能的取值/状态)、异常行为或抛出的异常(如有),以及必要时描述线程/调用约束或返回语义(尽管为
void)。确保注释格式符合项目约定(@param、@throws 等标签),并添加在 Greeting 接口的 handleMessage 方法声明上。
In
`@plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingMessage.java`:
- Around line 3-4: GreetingMessage 接口的方法 getGreetingUuid 缺少
Javadoc;请为接口和其方法补充完整的 Javadoc 注释,描述接口用途、方法功能、返回值含义(UUID
的格式/用途)以及任何异常或线程/并发相关的约束,确保在 GreetingMessage 接口文件顶部和 getGreetingUuid 方法签名上添加相应的
JavaDoc 块以满足文档和编码规范要求。
In
`@plugin/helloworld/src/main/java/org/zstack/plugin/example/HelloWorldManagerImpl.java`:
- Around line 60-69: The handle(APICreateGreetingMsg msg) method incorrectly
uses dbf.updateAndRefresh(vo) for a new GreetingVO; replace the update call with
dbf.persistAndRefresh(vo) so the new GreetingVO is inserted and refreshed
correctly (keep the UUID/greeting setup on GreetingVO as-is), and leave the
event creation (APICreateGreetingEvent evt) and inventory conversion
(vo.toInventory() or GreetingInventory.valueOf(vo)) unchanged.
🧹 Nitpick comments (3)
plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingBase.java (1)
45-49: 删除操作缺少已删除资源的信息返回。当前
APIDeleteGreetingEvent只包含消息 ID,建议在删除前将self转换为 inventory 并设置到事件中,以便调用方确认被删除的资源信息。这是 ZStack API 的常见模式。♻️ 建议的改进
private void handle(APIDeleteGreetingMsg msg) { + GreetingInventory inventory = GreetingInventory.valueOf(self); dbf.remove(self); - bus.publish(new APIDeleteGreetingEvent(msg.getId())); + APIDeleteGreetingEvent evt = new APIDeleteGreetingEvent(msg.getId()); + evt.setInventory(inventory); + bus.publish(evt); }plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingInventory.java (1)
26-28: 建议对集合参数进行空值检查。如果传入
null集合,当前实现会抛出NullPointerException。建议添加防御性检查。♻️ 建议的改进
public static List<GreetingInventory> valueOf(Collection<GreetingVO> vos) { + if (vos == null || vos.isEmpty()) { + return new ArrayList<>(); + } return vos.stream().map(GreetingInventory::valueOf).collect(Collectors.toList()); }需要额外导入:
import java.util.ArrayList;plugin/helloworld/src/main/java/org/zstack/plugin/example/HelloWorldManagerImpl.java (1)
71-77: 日志输出建议优化。Line 72 的日志输出直接打印
msg对象,建议使用更有意义的字段(如msg.getGreeting())来提高日志可读性。♻️ 建议的改进
private void handle(APIHelloWorldMsg msg) { - logger.debug(String.format("say hello: %s", msg)); + logger.debug(String.format("say hello: %s", msg.getGreeting())); APIHelloWorldEvent evt = new APIHelloWorldEvent(msg.getId()); evt.setGreeting(msg.getGreeting()); bus.publish(evt); }
| @RestRequest(path = "/helloworld/greetings", method = HttpMethod.POST, responseClass = APICreateGreetingEvent.class, parameterName = "params") | ||
| public class APICreateGreetingMsg extends APICreateMessage { | ||
| @APIParam(emptyString = false) | ||
| private String greeting; | ||
|
|
||
| public String getGreeting() { | ||
| return greeting; | ||
| } | ||
|
|
||
| public void setGreeting(String greeting) { | ||
| this.greeting = greeting; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
缺少 __example__,API 文档生成受阻。
API 消息类需要提供 __example__ 以生成文档与模板。建议补充示例方法。
🧩 建议补充
public class APICreateGreetingMsg extends APICreateMessage {
`@APIParam`(emptyString = false)
private String greeting;
@@
public void setGreeting(String greeting) {
this.greeting = greeting;
}
+
+ public static APICreateGreetingMsg __example__() {
+ APICreateGreetingMsg msg = new APICreateGreetingMsg();
+ msg.setGreeting("hello zstack");
+ return msg;
+ }
}基于编码规范。
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @RestRequest(path = "/helloworld/greetings", method = HttpMethod.POST, responseClass = APICreateGreetingEvent.class, parameterName = "params") | |
| public class APICreateGreetingMsg extends APICreateMessage { | |
| @APIParam(emptyString = false) | |
| private String greeting; | |
| public String getGreeting() { | |
| return greeting; | |
| } | |
| public void setGreeting(String greeting) { | |
| this.greeting = greeting; | |
| } | |
| `@RestRequest`(path = "/helloworld/greetings", method = HttpMethod.POST, responseClass = APICreateGreetingEvent.class, parameterName = "params") | |
| public class APICreateGreetingMsg extends APICreateMessage { | |
| `@APIParam`(emptyString = false) | |
| private String greeting; | |
| public String getGreeting() { | |
| return greeting; | |
| } | |
| public void setGreeting(String greeting) { | |
| this.greeting = greeting; | |
| } | |
| public static APICreateGreetingMsg __example__() { | |
| APICreateGreetingMsg msg = new APICreateGreetingMsg(); | |
| msg.setGreeting("hello zstack"); | |
| return msg; | |
| } | |
| } |
🤖 Prompt for AI Agents
In
`@plugin/helloworld/src/main/java/org/zstack/plugin/example/APICreateGreetingMsg.java`
around lines 9 - 20, The APICreateGreetingMsg class lacks the required static
__example__ method used for API doc generation; add a public static
APICreateGreetingMsg __example__() that constructs and returns an instance with
a representative greeting (e.g., "hello world") set via setGreeting, so
documentation and templates can be generated from this example; implement the
method inside the APICreateGreetingMsg class.
| import org.springframework.http.HttpMethod; | ||
| import org.zstack.header.message.APIDeleteMessage; | ||
| import org.zstack.header.rest.RestRequest; | ||
|
|
||
| @RestRequest(path = "/helloworld/greetings/{uuid}", method = HttpMethod.DELETE, responseClass = APIDeleteGreetingEvent.class) | ||
| public class APIDeleteGreetingMsg extends APIDeleteMessage implements GreetingMessage { | ||
| private String uuid; | ||
|
|
||
| public String getUuid() { | ||
| return uuid; | ||
| } | ||
|
|
||
| public void setUuid(String uuid) { | ||
| this.uuid = uuid; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
为 uuid 补充 @APIParam 以启用资源校验/权限检查。
未标注 resourceType/checkAccount 可能导致资源类型与账号校验缺失,删除类接口风险较高,建议补充。
🔧 建议补充参数校验注解
import org.springframework.http.HttpMethod;
import org.zstack.header.message.APIDeleteMessage;
+import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
`@RestRequest`(path = "/helloworld/greetings/{uuid}", method = HttpMethod.DELETE, responseClass = APIDeleteGreetingEvent.class)
public class APIDeleteGreetingMsg extends APIDeleteMessage implements GreetingMessage {
+ `@APIParam`(resourceType = GreetingVO.class, checkAccount = true)
private String uuid;🤖 Prompt for AI Agents
In
`@plugin/helloworld/src/main/java/org/zstack/plugin/example/APIDeleteGreetingMsg.java`
around lines 3 - 16, APIDeleteGreetingMsg 中的 uuid
缺少资源校验注解,导致删除接口无法触发资源类型与账号权限校验;在 APIDeleteGreetingMsg 类的 uuid 字段上添加
`@APIParam`(resourceType = GreetingVO.class, checkAccount = true)(或项目中对应的资源 VO
类型)以启用资源类型校验和账号检查,并确保 getter/setter 保持不变以避免序列化问题;确认导入对应的 `@APIParam` 注解和资源 VO
类(引用类名:APIDeleteGreetingMsg、uuid、GreetingMessage、APIDeleteMessage、GreetingVO)。
| public class APIDeleteGreetingMsg extends APIDeleteMessage implements GreetingMessage { | ||
| private String uuid; | ||
|
|
||
| public String getUuid() { | ||
| return uuid; | ||
| } | ||
|
|
||
| public void setUuid(String uuid) { | ||
| this.uuid = uuid; | ||
| } | ||
|
|
||
| @Override | ||
| public String getGreetingUuid() { | ||
| return uuid; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
补充 example 以生成 API 文档。
建议按现有示例模式添加 example。As per coding guidelines, 需补充 example 方法。
🧩 建议补充 __example__
`@Override`
public String getGreetingUuid() {
return uuid;
}
+
+ public static APIDeleteGreetingMsg __example__() {
+ APIDeleteGreetingMsg msg = new APIDeleteGreetingMsg();
+ msg.setUuid("c3f5e3b7b1fd4b1f9b6c2f3f1a2b3c4d");
+ return msg;
+ }
}🤖 Prompt for AI Agents
In
`@plugin/helloworld/src/main/java/org/zstack/plugin/example/APIDeleteGreetingMsg.java`
around lines 8 - 22, Add a static __example__ method to APIDeleteGreetingMsg
that returns a populated example instance (following other API classes' pattern)
so the API docs can be generated; implement it in the APIDeleteGreetingMsg class
(alongside getGreetingUuid) to construct a new APIDeleteGreetingMsg, set a
sample uuid (use the same sample/uuid helper or a realistic placeholder used in
other examples), and return that instance.
| @RestRequest(path = "/helloworld/greetings", method = HttpMethod.POST, responseClass = APIHelloWorldEvent.class) | ||
| public class APIHelloWorldMsg extends APIMessage { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
与 APICreateGreetingMsg 的 REST 路径/方法冲突。
当前 POST /helloworld/greetings 被两个消息复用,路由可能歧义或覆盖,建议改为唯一路径或不同方法。
🔧 建议调整为唯一 REST 路径
-@RestRequest(path = "/helloworld/greetings", method = HttpMethod.POST, responseClass = APIHelloWorldEvent.class)
+@RestRequest(path = "/helloworld/greetings/actions/hello", method = HttpMethod.POST, responseClass = APIHelloWorldEvent.class)
public class APIHelloWorldMsg extends APIMessage {🤖 Prompt for AI Agents
In
`@plugin/helloworld/src/main/java/org/zstack/plugin/example/APIHelloWorldMsg.java`
around lines 8 - 9, The REST mapping for APIHelloWorldMsg conflicts with
APICreateGreetingMsg because both use POST "/helloworld/greetings"; update
APIHelloWorldMsg's `@RestRequest` annotation to a unique path or HTTP method
(e.g., change path to "/helloworld/hello" or use GET) so routes are unambiguous,
then adjust the APIHelloWorldMsg class annotation and any related
client/tests/docs that reference the old path to match the new endpoint; ensure
you modify the `@RestRequest` on APIHelloWorldMsg and any routing/tests that
assert POST "/helloworld/greetings".
| public class APIHelloWorldMsg extends APIMessage { | ||
| @APIParam(maxLength = 255) | ||
| private String greeting; | ||
|
|
||
| public String getGreeting() { | ||
| return greeting; | ||
| } | ||
|
|
||
| public void setGreeting(String greeting) { | ||
| this.greeting = greeting; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
补充 example 以生成 API 文档。
缺少 example 会导致文档/模板缺失。As per coding guidelines, 需补充 example 方法。
🧩 建议补充 __example__
public void setGreeting(String greeting) {
this.greeting = greeting;
}
+
+ public static APIHelloWorldMsg __example__() {
+ APIHelloWorldMsg msg = new APIHelloWorldMsg();
+ msg.setGreeting("hello");
+ return msg;
+ }
}🤖 Prompt for AI Agents
In
`@plugin/helloworld/src/main/java/org/zstack/plugin/example/APIHelloWorldMsg.java`
around lines 9 - 19, Add a static example() factory to APIHelloWorldMsg so API
docs/templates are generated: implement a public static APIHelloWorldMsg
example() method that creates a new APIHelloWorldMsg instance, sets a
representative greeting (e.g., "hello world" or similar) via setGreeting, and
returns the instance; place this method inside the APIHelloWorldMsg class to
satisfy the documentation generator expectations.
| public interface Greeting { | ||
| void handleMessage(Message msg); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
接口方法缺少 Javadoc。
接口方法需配套有效 Javadoc,以满足规范与文档生成要求。
📝 建议补充
public interface Greeting {
+ /**
+ * 处理 Greeting 相关消息
+ */
void handleMessage(Message msg);
}基于编码规范。
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public interface Greeting { | |
| void handleMessage(Message msg); | |
| public interface Greeting { | |
| /** | |
| * 处理 Greeting 相关消息 | |
| */ | |
| void handleMessage(Message msg); |
🤖 Prompt for AI Agents
In `@plugin/helloworld/src/main/java/org/zstack/plugin/example/Greeting.java`
around lines 5 - 6, 接口方法 Greeting.handleMessage 缺少 Javadoc;请为该接口方法补充规范的 Javadoc
注释,描述方法的用途、参数(Message msg 的语义和可能的取值/状态)、异常行为或抛出的异常(如有),以及必要时描述线程/调用约束或返回语义(尽管为
void)。确保注释格式符合项目约定(@param、@throws 等标签),并添加在 Greeting 接口的 handleMessage 方法声明上。
| public interface GreetingMessage { | ||
| String getGreetingUuid(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
接口方法缺少 Javadoc。
接口方法需配套有效 Javadoc,以满足规范与文档生成要求。
📝 建议补充
public interface GreetingMessage {
+ /**
+ * 获取 Greeting 资源 UUID
+ */
String getGreetingUuid();
}基于编码规范。
🤖 Prompt for AI Agents
In
`@plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingMessage.java`
around lines 3 - 4, GreetingMessage 接口的方法 getGreetingUuid 缺少
Javadoc;请为接口和其方法补充完整的 Javadoc 注释,描述接口用途、方法功能、返回值含义(UUID
的格式/用途)以及任何异常或线程/并发相关的约束,确保在 GreetingMessage 接口文件顶部和 getGreetingUuid 方法签名上添加相应的
JavaDoc 块以满足文档和编码规范要求。
| private void handle(APICreateGreetingMsg msg) { | ||
| GreetingVO vo = new GreetingVO(); | ||
| vo.setUuid(msg.getResourceUuid() == null ? Platform.getUuid() : msg.getResourceUuid()); | ||
| vo.setGreeting(msg.getGreeting()); | ||
| vo = dbf.updateAndRefresh(vo); | ||
|
|
||
| APICreateGreetingEvent evt = new APICreateGreetingEvent(msg.getId()); | ||
| evt.setInventory(vo.toInventory()); | ||
| bus.publish(evt); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
fd -t f "GreetingVO.java" --exec cat {} \;Repository: MatheMatrix/zstack
Length of output: 1130
🏁 Script executed:
fd -t f "GreetingInventory.java" --exec cat {} \;Repository: MatheMatrix/zstack
Length of output: 1574
🏁 Script executed:
fd -t f "HelloWorldManagerImpl.java" --exec cat {} \;Repository: MatheMatrix/zstack
Length of output: 3012
🏁 Script executed:
# Search for persistAndRefresh and updateAndRefresh to understand their usage patterns
rg "persistAndRefresh|updateAndRefresh" -B 2 -A 2Repository: MatheMatrix/zstack
Length of output: 50375
🏁 Script executed:
# Search for ToInventory interface definition
fd -t f "ToInventory.java" --exec cat {} \;Repository: MatheMatrix/zstack
Length of output: 4388
🏁 Script executed:
# Search for ResourceVO and toInventory method
rg "class ResourceVO" -A 10Repository: MatheMatrix/zstack
Length of output: 2818
🏁 Script executed:
# Check if GreetingVO has toInventory by searching imports and method definitions
rg "toInventory" plugin/helloworld/src/main/java/org/zstack/plugin/example/ -B 2 -A 2Repository: MatheMatrix/zstack
Length of output: 641
Line 64: 新实体应使用 persistAndRefresh 而非 updateAndRefresh。
使用 dbf.updateAndRefresh(vo) 处理新创建的 GreetingVO 实体是不正确的。对于新实体的插入操作,应该使用 dbf.persistAndRefresh(vo)。updateAndRefresh 用于更新已存在的实体。
Line 67: vo.toInventory() 方法存在并可正常工作,因为 GreetingVO 实现了 ToInventory 接口。虽然使用 GreetingInventory.valueOf(vo) 会更加显式清晰,但当前代码并不会导致编译错误。
🐛 建议的修复
private void handle(APICreateGreetingMsg msg) {
GreetingVO vo = new GreetingVO();
vo.setUuid(msg.getResourceUuid() == null ? Platform.getUuid() : msg.getResourceUuid());
vo.setGreeting(msg.getGreeting());
- vo = dbf.updateAndRefresh(vo);
+ vo = dbf.persistAndRefresh(vo);
APICreateGreetingEvent evt = new APICreateGreetingEvent(msg.getId());
evt.setInventory(vo.toInventory());
bus.publish(evt);
}🤖 Prompt for AI Agents
In
`@plugin/helloworld/src/main/java/org/zstack/plugin/example/HelloWorldManagerImpl.java`
around lines 60 - 69, The handle(APICreateGreetingMsg msg) method incorrectly
uses dbf.updateAndRefresh(vo) for a new GreetingVO; replace the update call with
dbf.persistAndRefresh(vo) so the new GreetingVO is inserted and refreshed
correctly (keep the UUID/greeting setup on GreetingVO as-is), and leave the
event creation (APICreateGreetingEvent evt) and inventory conversion
(vo.toInventory() or GreetingInventory.valueOf(vo)) unchanged.
sync from gitlab !9055