|
9 | 9 | * Licensed under the Apache License, Version 2.0 (the "License"); |
10 | 10 | * you may not use this file except in compliance with the License. |
11 | 11 | * You may obtain a copy of the License at |
12 | | - * |
| 12 | + * |
13 | 13 | * http://www.apache.org/licenses/LICENSE-2.0 |
14 | | - * |
| 14 | + * |
15 | 15 | * Unless required by applicable law or agreed to in writing, software |
16 | 16 | * distributed under the License is distributed on an "AS IS" BASIS, |
17 | 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
21 | 21 | */ |
22 | 22 |
|
23 | 23 | import act.app.App; |
| 24 | +import act.cli.util.MappedFastJsonNameFilter; |
| 25 | +import act.data.DataPropertyRepository; |
24 | 26 | import com.alibaba.fastjson.JSON; |
25 | 27 | import com.alibaba.fastjson.parser.ParserConfig; |
26 | | -import com.alibaba.fastjson.serializer.SerializeConfig; |
27 | | -import com.alibaba.fastjson.serializer.SerializeFilter; |
28 | | -import com.alibaba.fastjson.serializer.SerializerFeature; |
| 28 | +import com.alibaba.fastjson.serializer.*; |
29 | 29 | import org.joda.time.DateTime; |
30 | 30 | import org.joda.time.LocalDate; |
31 | 31 | import org.joda.time.LocalDateTime; |
|
38 | 38 | import org.osgl.storage.impl.SObject; |
39 | 39 | import org.osgl.util.*; |
40 | 40 |
|
| 41 | +import java.io.IOException; |
| 42 | +import java.io.OutputStream; |
| 43 | +import java.io.Writer; |
| 44 | +import java.nio.charset.StandardCharsets; |
| 45 | +import java.util.HashSet; |
| 46 | +import java.util.LinkedHashSet; |
| 47 | +import java.util.List; |
| 48 | +import java.util.Set; |
| 49 | + |
41 | 50 | import static com.alibaba.fastjson.JSON.DEFAULT_GENERATE_FEATURE; |
42 | 51 |
|
43 | 52 | public class JsonUtilConfig { |
44 | | - public static void configure(App app) { |
| 53 | + |
| 54 | + public static class JsonWriter extends $.Visitor<org.osgl.util.Output> { |
| 55 | + |
| 56 | + private final Object v; |
| 57 | + private SerializerFeature[] features; |
| 58 | + private SerializeFilter[] filters; |
| 59 | + private String dateFormatPattern; |
| 60 | + private boolean hasPropFilter; |
| 61 | + |
| 62 | + public JsonWriter(Object v, PropertySpec.MetaInfo spec, boolean format, ActContext context) { |
| 63 | + if (null == v) { |
| 64 | + this.v = "{}"; |
| 65 | + } else if (v instanceof String) { |
| 66 | + String s = S.string(v).trim(); |
| 67 | + int len = s.length(); |
| 68 | + if (0 == len) { |
| 69 | + this.v = "{}"; |
| 70 | + } else { |
| 71 | + char a = s.charAt(0); |
| 72 | + char z = s.charAt(len - 1); |
| 73 | + if (('{' == a && '}' == z) || ('[' == a && ']' == z)) { |
| 74 | + this.v = s; |
| 75 | + } else { |
| 76 | + this.v = "{\"result\":" + s + "}"; |
| 77 | + } |
| 78 | + } |
| 79 | + } else { |
| 80 | + this.v = v; |
| 81 | + this.dateFormatPattern = null == context ? null : context.dateFormatPattern(); |
| 82 | + this.filters = initFilters(v, spec, context); |
| 83 | + this.features = initFeatures(format, context); |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + private SerializeFilter[] initFilters(Object v, PropertySpec.MetaInfo spec, ActContext context) { |
| 88 | + Set<SerializeFilter> filterSet = new LinkedHashSet<>(); |
| 89 | + FastJsonPropertyPreFilter propertyFilter = initPropertyPreFilter(v, spec, context); |
| 90 | + if (null != propertyFilter) { |
| 91 | + hasPropFilter = true; |
| 92 | + } |
| 93 | + if (null != spec && null != context) { |
| 94 | + MappedFastJsonNameFilter nameFilter = new MappedFastJsonNameFilter(spec.labelMapping(context)); |
| 95 | + filterSet.add(nameFilter); |
| 96 | + } |
| 97 | + |
| 98 | + if (null != context) { |
| 99 | + SerializeFilter[] filters = context.fastjsonFilters(); |
| 100 | + if (null != filters) { |
| 101 | + for (SerializeFilter f : filters) { |
| 102 | + filterSet.add(f); |
| 103 | + } |
| 104 | + } |
| 105 | + } |
| 106 | + if (null != propertyFilter) { |
| 107 | + filterSet.add(propertyFilter); |
| 108 | + } |
| 109 | + return filterSet.toArray(new SerializeFilter[filterSet.size()]); |
| 110 | + } |
| 111 | + |
| 112 | + private SerializerFeature[] initFeatures(boolean format, ActContext context) { |
| 113 | + Set<SerializerFeature> featureSet = new HashSet<>(); |
| 114 | + if (format) { |
| 115 | + featureSet.add(SerializerFeature.PrettyFormat); |
| 116 | + } |
| 117 | + if (null != context) { |
| 118 | + SerializerFeature[] features = context.fastjsonFeatures(); |
| 119 | + if (null != features) { |
| 120 | + for (SerializerFeature f : features) { |
| 121 | + featureSet.add(f); |
| 122 | + } |
| 123 | + } |
| 124 | + } |
| 125 | + Boolean b = DisableFastJsonCircularReferenceDetect.option.get(); |
| 126 | + if (null != b && b) { |
| 127 | + featureSet.add(SerializerFeature.DisableCircularReferenceDetect); |
| 128 | + } |
| 129 | + featureSet.add(SerializerFeature.WriteDateUseDateFormat); |
| 130 | + return featureSet.toArray(new SerializerFeature[featureSet.size()]); |
| 131 | + } |
| 132 | + |
| 133 | + private FastJsonPropertyPreFilter initPropertyPreFilter(Object v, PropertySpec.MetaInfo spec, ActContext context) { |
| 134 | + if (null != context) { |
| 135 | + spec = PropertySpec.MetaInfo.withCurrent(spec, context); |
| 136 | + } |
| 137 | + if (null == spec) { |
| 138 | + return null; |
| 139 | + } |
| 140 | + FastJsonPropertyPreFilter propertyFilter = new FastJsonPropertyPreFilter(); |
| 141 | + List<String> outputs = spec.outputFields(context); |
| 142 | + Set<String> excluded = spec.excludedFields(context); |
| 143 | + if (excluded.isEmpty()) { |
| 144 | + if (outputs.isEmpty()) { |
| 145 | + propertyFilter = null; // no filter defined actually |
| 146 | + } else { |
| 147 | + // output fields only applied when excluded fields not presented |
| 148 | + propertyFilter.addIncludes(outputs); |
| 149 | + if (FastJsonPropertyPreFilter.hasPattern(outputs)) { |
| 150 | + // TODO: handle the case when result is an Iterable |
| 151 | + propertyFilter.setFullPaths(context.app().service(DataPropertyRepository.class).propertyListOf(v.getClass())); |
| 152 | + } |
| 153 | + } |
| 154 | + } else { |
| 155 | + propertyFilter.addExcludes(excluded); |
| 156 | + if (FastJsonPropertyPreFilter.hasPattern(excluded)) { |
| 157 | + // TODO: handle the case when result is an Iterable |
| 158 | + propertyFilter.setFullPaths(context.app().service(DataPropertyRepository.class).propertyListOf(v.getClass())); |
| 159 | + } |
| 160 | + } |
| 161 | + return propertyFilter; |
| 162 | + } |
| 163 | + |
| 164 | + @Override |
| 165 | + public void visit(org.osgl.util.Output output) throws Osgl.Break { |
| 166 | + try { |
| 167 | + if (v instanceof CharSequence) { |
| 168 | + output.append((CharSequence) v); |
| 169 | + return; |
| 170 | + } |
| 171 | + if (output instanceof OutputStream) { |
| 172 | + OutputStream os = (OutputStream) output; |
| 173 | + JSON.writeJSONString(os, StandardCharsets.UTF_8, v, SerializeConfig.globalInstance, filters, dateFormatPattern, DEFAULT_GENERATE_FEATURE, features); |
| 174 | + } else if (output instanceof Writer) { |
| 175 | + Writer w = (Writer) output; |
| 176 | + writeJson(w, v, SerializeConfig.globalInstance, filters, dateFormatPattern, DEFAULT_GENERATE_FEATURE, features); |
| 177 | + } else { |
| 178 | + writeJson(output.asWriter(), v, SerializeConfig.globalInstance, filters, dateFormatPattern, DEFAULT_GENERATE_FEATURE, features); |
| 179 | + } |
| 180 | + } catch (IOException e) { |
| 181 | + throw E.ioException(e); |
| 182 | + } |
| 183 | + } |
| 184 | + } |
| 185 | + |
| 186 | + public static void configure(final App app) { |
45 | 187 | SerializeConfig config = SerializeConfig.getGlobalInstance(); |
46 | 188 |
|
47 | 189 | // patch https://github.com/alibaba/fastjson/issues/478 |
@@ -75,42 +217,41 @@ public static void configure(App app) { |
75 | 217 | parserConfig.putDeserializer(ISObject.class, sObjectCodec); |
76 | 218 | parserConfig.putDeserializer(SObject.class, sObjectCodec); |
77 | 219 |
|
78 | | - MvcConfig.jsonSerializer(new $.F1<Object, String>() { |
| 220 | + MvcConfig.jsonSerializer(new $.Func2<org.osgl.util.Output, Object, Void>() { |
79 | 221 | @Override |
80 | | - public String apply(Object o) throws NotAppliedException, Osgl.Break { |
81 | | - if (null == o) { |
82 | | - return "{}"; |
83 | | - } |
84 | | - ActContext<?> ctx = ActContext.Base.currentContext(); |
85 | | - SerializeFilter[] filters = null; |
86 | | - SerializerFeature[] features = null; |
87 | | - String dateFormatPattern = null; |
88 | | - if (null != ctx) { |
89 | | - filters = ctx.fastjsonFilters(); |
90 | | - features = ctx.fastjsonFeatures(); |
91 | | - dateFormatPattern = ctx.dateFormatPattern(); |
92 | | - } |
93 | | - Boolean b = DisableFastJsonCircularReferenceDetect.option.get(); |
94 | | - C.Set<SerializerFeature> featureSet = C.newSet(); |
95 | | - if (null != features) { |
96 | | - featureSet.addAll(C.listOf(features)); |
97 | | - } |
98 | | - featureSet.add(SerializerFeature.WriteDateUseDateFormat); |
99 | | - if (null != b && b) { |
100 | | - featureSet.add(SerializerFeature.DisableCircularReferenceDetect); |
101 | | - } |
102 | | - return null == dateFormatPattern ? |
103 | | - JSON.toJSONString( |
104 | | - o, filters, |
105 | | - featureSet.toArray(new SerializerFeature[featureSet.size()])) : |
106 | | - JSON.toJSONString( |
107 | | - o, |
108 | | - SerializeConfig.globalInstance, |
109 | | - filters, |
110 | | - dateFormatPattern, |
111 | | - DEFAULT_GENERATE_FEATURE, |
112 | | - featureSet.toArray(new SerializerFeature[featureSet.size()])); |
| 222 | + public Void apply(org.osgl.util.Output appendable, Object v) throws NotAppliedException, Osgl.Break { |
| 223 | + new JsonWriter(v, null, false, ActContext.Base.currentContext()).apply(appendable); |
| 224 | + return null; |
113 | 225 | } |
114 | 226 | }); |
115 | 227 | } |
| 228 | + |
| 229 | + // FastJSON does not provide the API so we have to create our own |
| 230 | + private static final void writeJson(Writer os, // |
| 231 | + Object object, // |
| 232 | + SerializeConfig config, // |
| 233 | + SerializeFilter[] filters, // |
| 234 | + String dateFormat, // |
| 235 | + int defaultFeatures, // |
| 236 | + SerializerFeature... features) { |
| 237 | + SerializeWriter writer = new SerializeWriter(os, defaultFeatures, features); |
| 238 | + |
| 239 | + try { |
| 240 | + JSONSerializer serializer = new JSONSerializer(writer, config); |
| 241 | + |
| 242 | + if (dateFormat != null && dateFormat.length() != 0) { |
| 243 | + serializer.setDateFormat(dateFormat); |
| 244 | + serializer.config(SerializerFeature.WriteDateUseDateFormat, true); |
| 245 | + } |
| 246 | + |
| 247 | + if (filters != null) { |
| 248 | + for (SerializeFilter filter : filters) { |
| 249 | + serializer.addFilter(filter); |
| 250 | + } |
| 251 | + } |
| 252 | + serializer.write(object); |
| 253 | + } finally { |
| 254 | + writer.close(); |
| 255 | + } |
| 256 | + } |
116 | 257 | } |
0 commit comments