001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.json; 014 015import java.util.*; 016import java.util.concurrent.*; 017 018import org.apache.juneau.*; 019import org.apache.juneau.annotation.*; 020import org.apache.juneau.collections.*; 021import org.apache.juneau.jsonschema.*; 022import org.apache.juneau.serializer.*; 023 024/** 025 * Serializes POJO metadata to HTTP responses as JSON-Schema. 026 * 027 * <h5 class='topic'>Media types</h5> 028 * 029 * Handles <c>Accept</c> types: <bc>application/json+schema, text/json+schema</bc> 030 * <p> 031 * Produces <c>Content-Type</c> types: <bc>application/json</bc> 032 * 033 * <h5 class='topic'>Description</h5> 034 * 035 * Produces the JSON-schema for the JSON produced by the {@link JsonSerializer} class with the same properties. 036 */ 037@ConfigurableContext 038public class JsonSchemaSerializer extends JsonSerializer implements JsonSchemaMetaProvider { 039 040 //------------------------------------------------------------------------------------------------------------------- 041 // Configurable properties 042 //------------------------------------------------------------------------------------------------------------------- 043 044 static final String PREFIX = "JsonSchemaSerializer"; 045 046 //------------------------------------------------------------------------------------------------------------------- 047 // Predefined instances 048 //------------------------------------------------------------------------------------------------------------------- 049 050 /** Default serializer, all default settings.*/ 051 public static final JsonSchemaSerializer DEFAULT = new JsonSchemaSerializer(PropertyStore.DEFAULT); 052 053 /** Default serializer, all default settings.*/ 054 public static final JsonSchemaSerializer DEFAULT_READABLE = new Readable(PropertyStore.DEFAULT); 055 056 /** Default serializer, single quotes, simple mode. */ 057 public static final JsonSchemaSerializer DEFAULT_SIMPLE = new Simple(PropertyStore.DEFAULT); 058 059 /** Default serializer, single quotes, simple mode, with whitespace. */ 060 public static final JsonSchemaSerializer DEFAULT_SIMPLE_READABLE = new SimpleReadable(PropertyStore.DEFAULT); 061 062 063 //------------------------------------------------------------------------------------------------------------------- 064 // Predefined subclasses 065 //------------------------------------------------------------------------------------------------------------------- 066 067 /** Default serializer, with whitespace. */ 068 public static class Readable extends JsonSchemaSerializer { 069 070 /** 071 * Constructor. 072 * 073 * @param ps The property store containing all the settings for this object. 074 */ 075 public Readable(PropertyStore ps) { 076 super( 077 ps.builder().setDefault(WSERIALIZER_useWhitespace, true).build() 078 ); 079 } 080 } 081 082 /** Default serializer, single quotes, simple mode. */ 083 public static class Simple extends JsonSchemaSerializer { 084 085 /** 086 * Constructor. 087 * 088 * @param ps The property store containing all the settings for this object. 089 */ 090 public Simple(PropertyStore ps) { 091 super( 092 ps.builder() 093 .setDefault(JSON_simpleMode, true) 094 .setDefault(WSERIALIZER_quoteChar, '\'') 095 .build() 096 ); 097 } 098 } 099 100 /** Default serializer, single quotes, simple mode, with whitespace. */ 101 public static class SimpleReadable extends JsonSchemaSerializer { 102 103 /** 104 * Constructor. 105 * 106 * @param ps The property store containing all the settings for this object. 107 */ 108 public SimpleReadable(PropertyStore ps) { 109 super( 110 ps.builder() 111 .setDefault(JSON_simpleMode, true) 112 .setDefault(WSERIALIZER_quoteChar, '\'') 113 .setDefault(WSERIALIZER_useWhitespace, true) 114 .build() 115 ); 116 } 117 } 118 119 120 //------------------------------------------------------------------------------------------------------------------- 121 // Instance 122 //------------------------------------------------------------------------------------------------------------------- 123 124 private final JsonSchemaGenerator generator; 125 private final Map<ClassMeta<?>,JsonSchemaClassMeta> jsonSchemaClassMetas = new ConcurrentHashMap<>(); 126 private final Map<BeanPropertyMeta,JsonSchemaBeanPropertyMeta> jsonSchemaBeanPropertyMetas = new ConcurrentHashMap<>(); 127 128 /** 129 * Constructor. 130 * 131 * @param ps Initialize with the specified config property store. 132 */ 133 public JsonSchemaSerializer(PropertyStore ps) { 134 super( 135 ps.builder() 136 .setDefault(BEANTRAVERSE_detectRecursions, true) 137 .setDefault(BEANTRAVERSE_ignoreRecursions, true) 138 .build(), 139 "application/json", "application/json+schema,text/json+schema" 140 ); 141 142 generator = JsonSchemaGenerator.create().apply(getPropertyStore()).build(); 143 } 144 145 @Override /* Context */ 146 public JsonSchemaSerializerBuilder builder() { 147 return new JsonSchemaSerializerBuilder(getPropertyStore()); 148 } 149 150 /** 151 * Instantiates a new clean-slate {@link JsonSerializerBuilder} object. 152 * 153 * <p> 154 * This is equivalent to simply calling <code><jk>new</jk> JsonSerializerBuilder()</code>. 155 * 156 * @return A new {@link JsonSerializerBuilder} object. 157 */ 158 public static JsonSchemaSerializerBuilder create() { 159 return new JsonSchemaSerializerBuilder(); 160 } 161 162 @Override /* Context */ 163 public JsonSchemaSerializerSession createSession() { 164 return createSession(createDefaultSessionArgs()); 165 } 166 167 @Override /* Serializer */ 168 public JsonSchemaSerializerSession createSession(SerializerSessionArgs args) { 169 return new JsonSchemaSerializerSession(this, args); 170 } 171 172 JsonSchemaGenerator getGenerator() { 173 return generator; 174 } 175 176 //----------------------------------------------------------------------------------------------------------------- 177 // Extended metadata 178 //----------------------------------------------------------------------------------------------------------------- 179 180 @Override /* JsonSchemaMetaProvider */ 181 public JsonSchemaClassMeta getJsonSchemaClassMeta(ClassMeta<?> cm) { 182 JsonSchemaClassMeta m = jsonSchemaClassMetas.get(cm); 183 if (m == null) { 184 m = new JsonSchemaClassMeta(cm, this); 185 jsonSchemaClassMetas.put(cm, m); 186 } 187 return m; 188 } 189 190 @Override /* JsonSchemaMetaProvider */ 191 public JsonSchemaBeanPropertyMeta getJsonSchemaBeanPropertyMeta(BeanPropertyMeta bpm) { 192 JsonSchemaBeanPropertyMeta m = jsonSchemaBeanPropertyMetas.get(bpm); 193 if (m == null) { 194 m = new JsonSchemaBeanPropertyMeta(bpm.getDelegateFor(), this); 195 jsonSchemaBeanPropertyMetas.put(bpm, m); 196 } 197 return m; 198 } 199 200 //----------------------------------------------------------------------------------------------------------------- 201 // Other methods 202 //----------------------------------------------------------------------------------------------------------------- 203 204 @Override /* Context */ 205 public OMap toMap() { 206 return super.toMap() 207 .a("JsonSchemaSerializer", new DefaultFilteringOMap() 208 .a("generator", generator) 209 ); 210 } 211}