xwiki 文档迁移到 confluence-(4) Jackson 如何动态生成 key

问题的由来

在讨论这个话题之前, 先回顾下之前第二节-如何将上传的 pdf 显示在 confluence 页面中的最后一个请求, 更新 drafts.看下其请求数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
"status":"current",
"title":"测试文档",
"space":{
"key":"SPC"
},
"body":{
"editor":{
"value":"<p><img class="editor-inline-macro" height="250" src="http://localhost:8090/rest/documentConversion/latest/conversion/thumbnail/4849682/1?attachmentId=4849682&version=1&mimeType=application%2Fpdf&height=250&thumbnailStatus=200" data-macro-name="view-file" data-macro-parameters="height=250|name=测试xxx.pdf" data-macro-schema-version="1" /></p>",
"representation":"editor",
"content":{
"id":"2818258"
}
}
},
"id":"2818258",
"type":"page",
"version":{
"number":18,
"message":"",
"minorEdit":false,
"syncRev":"0.AjEmNKvqWOSCexKgWizfyQ0.3"
},
"ancestors":[
{
"id":"65584",
"type":"page"
}
]
}

需要关注是 body 部分.其中有一个 editor 节点.因为之前在导入 confluence 相关 jar 的时候, 发现有一个类 ContentRepresentation:

1

可以看到里面定义了一系列的动作, rawstorageeditor 等.所以再看看上面的请求, 觉得这个 body.editor 中的 editor 应该不是一个静态的值, 而是动态的, 也就是说如果是 storage 相关的动作, 那么请求可能是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"status":"current",
"title":"测试文档",
"space":{
"key":"SPC"
},
"body":{
"storage":{
"value":"...",
"representation":"storage",
"content":{
"id":"2818258"
}
}
}
}

下图进行了下对比:

2

那么究竟是不是这样的, 我也没有进行过进一步的考究.不过我想, 不管是不是这样的, 假如遇到这种情况的话, 我们该怎么做.如何把对象序列化为这样的 JSON 格式.当然可以简单粗暴地将就将字段直接声明为 storage 活着 editor, 是一种办法, 就是不够好.接下来就看看如何使用 Jackson 实现这样的情况:

Jackson 动态生成 key

自定义注解

首选我们自定义一个注解, 就是说如果 Jackson 在解析的时候如果遇到这样的注解, 使用我们自己的逻辑进行处理.

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface RepresentationType {
}

在需要进行动态生成 key 的字段上加上此注解:

1
2
3
4
5
6
7
8
@Data
public class WikiContentBody {
/**
* 根据不同的 Representation 生成对应的 key
*/
@RepresentationType
private WikiContentRepresentation contentRepresentation;
}

实现自定义的 JacksonAnnotationIntrospector

这一步需要继承 JacksonAnnotationIntrospector 类, 并覆盖 isAnnotationBundle()findNameForSerialization() 等方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 动态替换 WikiContentBody#contentRepresentation 在序列化过程中的名称.
*/
public class RepresentationTypeSerializer extends JacksonAnnotationIntrospector implements Versioned {
@Override
public boolean isAnnotationBundle(Annotation ann) {
return false;
}

@Override
public PropertyName findNameForSerialization(Annotated a) {
return null;
}
}

将 JacksonAnnotationIntrospector 设置到 Jackson 序列化过程中

要让 Jackson 使用我们自定义的 RepresentationTypeSerializer 实现, 简单的将其设置到 ObjectMapper 中即可:

1
2
3
4
ObjectMapper objectMapper = new ObjectMapper();
AnnotationIntrospector representationTypeSerializer = new RepresentationTypeSerializer();
// 使用我们自定义的 AnnotationIntrospector
objectMapper.setAnnotationIntrospector(representationTypeSerializer);

RepresentationTypeSerializer 的具体实现

上面我们只是稍微简单的说明了下 RepresentationTypeSerializer 需要覆盖哪些方法, 并没有给出具体的实现.首先是 isAnnotationBundle() 方法, 这个方法毕竟简单, 具体实现如下:

1
2
3
4
5
6
@Override
public boolean isAnnotationBundle(Annotation ann) {
Class<?> cls = ann.annotationType();
// 是否可以处理 RepresentationType 注解
return RepresentationType.class == cls || super.isAnnotationBundle(ann);
}

接下来是 findNameForSerialization() 方法.先看下此方法的签名:

1
2
public PropertyName findNameForSerialization(Annotated a) {
}

方法中只有一个参数 Annotated, 那我们如何知道怎么进行替换呢? 替换成什么值呢? Annotated 似乎并没有提供什么有用的方法, 没法取到 WikiContentBody 中的 contentRepresentation 字段值.

利用 ThreadLocal 取得动态动态 key

刚刚问题就卡在了 findNameForSerialization(Annotated a) 方法没有办法取得动态的 key.只要解决了这个问题, 那么一切都可以顺利进行了.

这个时候就得搬出 ThreadLocal.也就是说在序列化之前将要动态生成的 key 放到 ThreadLocal 里.看下具体的实现.

首先是 ThreadLocal 的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RepresentationTypeThreadLocal {
private static ThreadLocal<ContentRepresentation> CR_THREAD_LOCAL = new ThreadLocal<>();

public static void set(ContentRepresentation representation) {
CR_THREAD_LOCAL.set(representation);
}

public static ContentRepresentation get() {
return CR_THREAD_LOCAL.get();
}

public static void remove() {
CR_THREAD_LOCAL.remove();
}
}

接下来是 findNameForSerialization() 方法的具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public PropertyName findNameForSerialization(Annotated a) {
RepresentationType type = a.getAnnotation(RepresentationType.class);
if (type != null) {
String name = getPropertyName();
if (name != null) {
return PropertyName.construct(name);
}
}

return super.findNameForSerialization(a);
}


private String getPropertyName() {
// 利用 ThreadLocal 取得动态 key
ContentRepresentation representation = RepresentationTypeThreadLocal.get();
if (representation != null) {
return representation.getRepresentation();
}

...
}

在序列化之前首先先调用类似于如下的代码:

1
RepresentationTypeThreadLocal.set(ContentRepresentation.EDITOR);

测试打印结果如下:

3

xwiki 文档迁移到 confluence-1 迁移方案
xwiki 文档迁移到 confluence-(3) 遇到的坑及注意事项