001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.bean.jsonapi; 018 019import static org.apache.juneau.commons.utils.CollectionUtils.*; 020import static org.apache.juneau.commons.utils.Shorts.*; 021 022import java.util.*; 023 024import org.apache.juneau.commons.bean.*; 025import org.apache.juneau.marshall.*; 026import org.apache.juneau.marshall.marshaller.*; 027 028/** 029 * Represents the top-level JSON:API document as defined by 030 * <a href="https://jsonapi.org/format/#document-top-level">JSON:API v1.1 § Document Structure</a>. 031 * 032 * <p> 033 * Carries the standard six top-level members: {@code data}, {@code errors}, {@code meta}, {@code jsonapi}, 034 * {@code links}, {@code included}. 035 * 036 * <h5 class='topic'>Mutual exclusion</h5> 037 * 038 * <p> 039 * Per spec, a document MUST contain at least one of {@code data}/{@code errors}/{@code meta}, and {@code data} 040 * and {@code errors} MUST NOT coexist. The {@link #validate()} helper checks both rules and throws 041 * {@link IllegalStateException} on violation. 042 * 043 * <h5 class='topic'>The polymorphic {@code data} member</h5> 044 * 045 * <p> 046 * The top-level {@code data} can be {@code null}, a single {@link JsonApiResource}, or an array of them. We 047 * model {@code data} as a plain {@code Object} property; the writer chooses single-vs-array shape based on 048 * which value was set. Typed setters {@link #setData(JsonApiResource)} and {@link #setData(List)} are provided 049 * for convenience. 050 * 051 * <h5 class='section'>See Also:</h5><ul> 052 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanJsonApi">juneau-bean-jsonapi</a> 053 * </ul> 054 */ 055@Marshalled 056public class JsonApiDocument { 057 058 private Object data; 059 private List<JsonApiError> errors; 060 private Map<String,Object> meta; 061 private JsonApiVersion jsonapi; 062 private Map<String,Object> links; 063 private List<JsonApiResource> included; 064 065 /** 066 * Default constructor. 067 */ 068 public JsonApiDocument() { /* intentionally empty */ } 069 070 /** 071 * Bean property getter: <property>data</property>. 072 * 073 * <p> 074 * Top-level resource linkage. May be {@code null}, a single {@link JsonApiResource}, or a 075 * {@code List<JsonApiResource>}. 076 * 077 * @return The value of the <property>data</property> property, or <jk>null</jk> if it is not set. 078 */ 079 public Object getData() { return data; } 080 081 /** 082 * Bean property setter: <property>data</property>. 083 * 084 * @param value The new value (single resource, list of resources, or {@code null} to unset). 085 * @return This object. 086 */ 087 public JsonApiDocument setData(Object value) { 088 data = value; 089 return this; 090 } 091 092 /** 093 * Typed setter for single-resource primary data. 094 * 095 * @param value The resource. Can be <jk>null</jk> to unset the property. 096 * @return This object. 097 */ 098 public JsonApiDocument setData(JsonApiResource value) { 099 data = value; 100 return this; 101 } 102 103 /** 104 * Typed setter for to-many primary data. 105 * 106 * @param value The list of resources. Can be <jk>null</jk> to unset the property. 107 * @return This object. 108 */ 109 public JsonApiDocument setData(List<JsonApiResource> value) { 110 data = value; 111 return this; 112 } 113 114 /** 115 * Bean property getter: <property>errors</property>. 116 * 117 * @return The value of the <property>errors</property> property, or <jk>null</jk> if it is not set. 118 */ 119 public List<JsonApiError> getErrors() { return u(errors); } 120 121 /** 122 * Bean property setter: <property>errors</property>. 123 * 124 * @param value The new value for this property. Can be <jk>null</jk> to unset the property. 125 * @return This object. 126 */ 127 public JsonApiDocument setErrors(List<JsonApiError> value) { 128 errors = value; 129 return this; 130 } 131 132 /** 133 * Bean property setter: <property>errors</property>. 134 * 135 * @param value The new value for this property. 136 * @return This object. 137 */ 138 public JsonApiDocument setErrors(JsonApiError...value) { 139 errors = list(value); 140 return this; 141 } 142 143 /** 144 * Bean property appender: <property>errors</property>. 145 * 146 * @param value The error objects to append. 147 * @return This object. 148 */ 149 public JsonApiDocument addErrors(JsonApiError...value) { 150 if (errors == null) 151 errors = list(); 152 Collections.addAll(errors, value); 153 return this; 154 } 155 156 /** 157 * Bean property appender: <property>errors</property>. 158 * 159 * @param value The error objects to append. 160 * @return This object. 161 */ 162 public JsonApiDocument addErrors(Collection<JsonApiError> value) { 163 if (errors == null) 164 errors = list(); 165 errors.addAll(value); 166 return this; 167 } 168 169 /** 170 * Bean property getter: <property>meta</property>. 171 * 172 * @return The value of the <property>meta</property> property, or <jk>null</jk> if it is not set. 173 */ 174 public Map<String,Object> getMeta() { return u(meta); } 175 176 /** 177 * Bean property setter: <property>meta</property>. 178 * 179 * @param value The new value for this property. Can be <jk>null</jk> to unset the property. 180 * @return This object. 181 */ 182 public JsonApiDocument setMeta(Map<String,Object> value) { 183 meta = value; 184 return this; 185 } 186 187 /** 188 * Convenience method to add a single meta entry. 189 * 190 * @param name The meta key. Can be <jk>null</jk> ({@link LinkedHashMap} tolerates a <jk>null</jk> key). 191 * @param value The meta value. Can be <jk>null</jk> (stored as <jk>null</jk>). 192 * @return This object. 193 */ 194 public JsonApiDocument putMeta(String name, Object value) { 195 if (meta == null) 196 meta = map(); 197 meta.put(name, value); 198 return this; 199 } 200 201 /** 202 * Bean property getter: <property>jsonapi</property>. 203 * 204 * @return The value of the <property>jsonapi</property> property, or <jk>null</jk> if it is not set. 205 */ 206 public JsonApiVersion getJsonapi() { return jsonapi; } 207 208 /** 209 * Bean property setter: <property>jsonapi</property>. 210 * 211 * @param value The new value for this property. Can be <jk>null</jk> to unset the property. 212 * @return This object. 213 */ 214 public JsonApiDocument setJsonapi(JsonApiVersion value) { 215 jsonapi = value; 216 return this; 217 } 218 219 /** 220 * Bean property getter: <property>links</property>. 221 * 222 * <p> 223 * Per spec, each {@code links} value can be either a JSON string URL or a {@link JsonApiLink} object. 224 * 225 * @return The value of the <property>links</property> property, or <jk>null</jk> if it is not set. 226 */ 227 @BeanProp(value="links") 228 @Swap(JsonApiLinkOrStringSwap.class) 229 public Map<String,Object> getLinks() { return u(links); } 230 231 /** 232 * Bean property setter: <property>links</property>. 233 * 234 * @param value The new value for this property. Can be <jk>null</jk> to unset the property. 235 * @return This object. 236 */ 237 @BeanProp(value="links") 238 @Swap(JsonApiLinkOrStringSwap.class) 239 public JsonApiDocument setLinks(Map<String,Object> value) { 240 links = value; 241 return this; 242 } 243 244 /** 245 * Convenience method to add a single link. 246 * 247 * @param name The link name. Can be <jk>null</jk> ({@link LinkedHashMap} tolerates a <jk>null</jk> key). 248 * @param value The link value (a {@link String} URL or a {@link JsonApiLink} object). Can be <jk>null</jk> (stored as <jk>null</jk>). 249 * @return This object. 250 */ 251 public JsonApiDocument putLink(String name, Object value) { 252 if (links == null) 253 links = map(); 254 links.put(name, value); 255 return this; 256 } 257 258 /** 259 * Bean property getter: <property>included</property>. 260 * 261 * @return The value of the <property>included</property> property, or <jk>null</jk> if it is not set. 262 */ 263 public List<JsonApiResource> getIncluded() { return u(included); } 264 265 /** 266 * Bean property setter: <property>included</property>. 267 * 268 * @param value The new value for this property. Can be <jk>null</jk> to unset the property. 269 * @return This object. 270 */ 271 public JsonApiDocument setIncluded(List<JsonApiResource> value) { 272 included = value; 273 return this; 274 } 275 276 /** 277 * Bean property setter: <property>included</property>. 278 * 279 * @param value The new value for this property. 280 * @return This object. 281 */ 282 public JsonApiDocument setIncluded(JsonApiResource...value) { 283 included = list(value); 284 return this; 285 } 286 287 /** 288 * Bean property appender: <property>included</property>. 289 * 290 * @param value The resource objects to append. 291 * @return This object. 292 */ 293 public JsonApiDocument addIncluded(JsonApiResource...value) { 294 if (included == null) 295 included = list(); 296 Collections.addAll(included, value); 297 return this; 298 } 299 300 /** 301 * Bean property appender: <property>included</property>. 302 * 303 * @param value The resource objects to append. 304 * @return This object. 305 */ 306 public JsonApiDocument addIncluded(Collection<JsonApiResource> value) { 307 if (included == null) 308 included = list(); 309 included.addAll(value); 310 return this; 311 } 312 313 /** 314 * Validates document-level invariants per the JSON:API spec. 315 * 316 * <p> 317 * Throws if {@code data} and {@code errors} are both present, or if none of {@code data}/{@code errors}/{@code meta} 318 * is present. 319 * 320 * @return This object. 321 * @throws IllegalStateException If the document violates a top-level invariant. 322 */ 323 public JsonApiDocument validate() { 324 if (data != null && errors != null) 325 throw isex("JSON:API document must not contain both 'data' and 'errors'."); 326 if (data == null && errors == null && meta == null) 327 throw isex("JSON:API document must contain at least one of 'data', 'errors', or 'meta'."); 328 return this; 329 } 330 331 @Override /* Overridden from Object */ 332 public String toString() { 333 return Json.of(this); 334 } 335}